5

I have the following code. I expect it to print:

A
B
C
DONE

instead it prints

P
P
P
DONE

why?

UPDATE
I'm not asking for a work around solution. I want to know why this is happening. I thought generics were resolved at compile time. From what I can tell it should be able to resolve these to the proper methods at compile time, but apparently it is not and I do not understand why. I am looking for an explanation of why, not a work around solution.

here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication50
{
    class Parent
    {
        public string FieldName { get; set; }
        public string Id { get; set; }
    }

    class ChildA : Parent
    {
        public string FieldValue { get; set; }
    }

    class ChildB : Parent
    {
        public DateTime? Start { get; set; }
        public DateTime? End { get; set; }
    }

    class ChildC : Parent
    {
        public ICollection<string> Values { get; set; }
    }
    class Program
    {
        void Validate<T>(Parent item) where T : Parent
        {
            if (item is T)
                Validate(item as T);
        }
        void Validate(ChildA filter)
        {
            Console.WriteLine("A");
        }

        void Validate(ChildB filter)
        {
            Console.WriteLine("B");
        }

        void Validate(ChildC filter)
        {
            Console.WriteLine("C");
        }

        void Validate(Parent filter)
        {
            Console.WriteLine("P");
            // do nothing placeholder so the code will compile
        }

        ArgumentException Fail(Parent filter, string message)
        {
            return new ArgumentException(message, filter.FieldName);
        }

        void Run()
        {
            var list = new List<Parent> {
                new ChildA(), new ChildB(), new ChildC() };
            Validate<ChildA>(list[0]);
            Validate<ChildB>(list[1]);
            Validate<ChildC>(list[2]);
        }
        public static void Main()
        {
            new Program().Run();
            Console.WriteLine();
            Console.WriteLine("DONE");
            Console.ReadLine();
        }
    }
}
Charles Lambert
  • 5,042
  • 26
  • 47
  • 3
    Check out this blog post by Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2009/07/30/generics-are-not-templates.aspx – juharr May 25 '11 at 16:50
  • @juharr - Looks like all blogs.msdn.com links return 403 now. – Alex Aug 28 '23 at 06:09

4 Answers4

14

Generics are a run-time concept. This is their primary difference from C++ templates, which are a compile-time concept.

Within the method Validate<T>, T is always unknown at compile-time, even when explicitly specified by the caller. The only thing Validate<T> knows about T is that it descends from Parent.

More specifically, generics cannot be used to generate code. What you're trying would work under C++ because when C++ sees a call to Validate<ClassA>, it actually recompiles Validate<T>, so templates become a kind of code generation. Under C#, Validate<T> is only compiled once, so generics cannot be used as a kind of code generation.

Under C++, the call to Validate<ClassA> will instantiate the template at compile-time.

Under C#, the call to Validate<ClassA> will instatiate the generic method at run-time.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 2
    It's worthwhile to note that even without Reflection, it's possible in .NET to have a method which, when passed in a string consisting of the letters X and Y (e.g. "XYXXYXY"), would call a generic method with type `X>>>>>>`, effectively demonstrating that the number of different classes that a single .NET program could potentially use is essentially boundless--a situation very different from C++ where each distinct type would need to have code associated with it. – supercat Jul 18 '13 at 22:08
  • >>Generics are a run-time concept. NO! this answer is incorrect. Sorry, this is totally incorrect. Generics in C# are compiled time. Compiler generates concrete code and instructions for them according to usage. There is even a problem of extra code generated by compiler which even has a solution (optimization of number of pre-compiled libs). ref - J.Richter 4th editions, Generics, page 314. Otherwise it will neve be the same fast as original non-generic code, but equal to reflection. – Artem A Feb 20 '22 at 16:00
5

Overload resolution is performed at compile-time, not at runtime.

The usual solution is to use simple virtual dispatch here:

class Parent
{
    public virtual  void Validate() { Console.WriteLine("P"); }
}

class ChildA : Parent
{
    public override void Validate() { Console.WriteLine("A"); }
}

class ChildB : Parent
{
    public override void Validate() { Console.WriteLine("B"); }
}

void Run()
{
    var list = new List<Parent> { new ChildA(), new ChildB() };
    list[0].Validate(); // prints "A"
    list[1].Validate(); // prints "B"
}
dtb
  • 213,145
  • 36
  • 401
  • 431
  • 1
    This works as expected, but it isn't related to Visitor. It's simple virtual dispatch. – Jon May 25 '11 at 16:50
2

The item will ALWAYS be validated as type Parent.

The idea behind generics is that you do NOT have type specific code.. hence the "generic" part of it.

You are limiting to a branch of classes, in this case Parent and everything that descends from it. This means the code should execute as if the object being passed in was of type Parent.

As dtb said, the Visitor pattern could apply here.

Another idea would be to simply have an interface which defined the Validate() method that each class had to support. I think this would be a better route to take.

NotMe
  • 87,343
  • 27
  • 171
  • 245
2

Assuming you can use C# 4.0, you can defeat the static overload resolution that the C# compiler will do by using the "dynamic" keyword. Simply change your validate function to:

    void Validate<T>(Parent item) where T : Parent
    {
        dynamic dyn = item;
        if (item is T)
            Validate(dyn);
    }

and the output will be:

C:\tmp>temp.exe
A
B
C

DONE

I just learned this myself from @juharr's link to Eric Lippert's blog. Read more about the dynamic type on msdn.

GrantJ
  • 8,162
  • 3
  • 52
  • 46