почему этот общий не разрешен во время компиляции?

#c# #generics

#c# #обобщения

Вопрос:

У меня есть следующий код. Я ожидаю, что он напечатает:

 A
B
C
DONE
  

вместо этого он печатает

 P
P
P
DONE
  

почему?

Обновить
Я не прошу обходного решения. Я хочу знать, почему это происходит. Я думал, что дженерики были разрешены во время компиляции. Из того, что я могу сказать, это должно быть в состоянии разрешить их с помощью надлежащих методов во время компиляции, но, по-видимому, это не так, и я не понимаю почему. Я ищу объяснение почему, а не обходное решение.

вот код:

 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();
        }
    }
}
  

Комментарии:

1. Ознакомьтесь с этим сообщением в блоге Эрика Липперта blogs.msdn.com/b/ericlippert/archive/2009/07/30 /…

Ответ №1:

Обобщения — это концепция времени выполнения. В этом их основное отличие от шаблонов C , которые являются концепцией времени компиляции.

Во время компиляции метод Validate<T> T всегда неизвестен, даже если он явно указан вызывающей стороной. Единственное, о Validate<T> чем T известно, это то, что он происходит от Parent .

Более конкретно, общие файлы нельзя использовать для генерации кода. То, что вы пытаетесь, сработало бы под C , потому что, когда C видит вызов Validate<ClassA> , он фактически перекомпилируется Validate<T> , поэтому шаблоны становятся своего рода генерацией кода. В C # Validate<T> компилируется только один раз, поэтому дженерики нельзя использовать как своего рода генерацию кода.

В C вызов Validate<ClassA> создаст экземпляр шаблона во время компиляции.

В C # вызов Validate<ClassA> будет инициализировать универсальный метод во время выполнения.

Комментарии:

1. Стоит отметить, что даже без отражения в .NET возможно иметь метод, который при передаче в строке, состоящей из букв X и Y (например, «XYXXYXY»), вызывал бы универсальный метод с типом X<Y<X<X<Y<X<Y<fnord>>>>>>> , эффективно демонстрируя, что количество различных классов, которые один . Потенциально возможная программа NET может использовать по существу безграничный — ситуация, сильно отличающаяся от C , где каждому отдельному типу необходимо было бы иметь связанный с ним код.

2. >> Обобщения — это концепция времени выполнения. НЕТ! этот ответ неверен. Извините, это совершенно неверно. Обобщения в C # выполняются во время компиляции. Компилятор генерирует конкретный код и инструкции для них в соответствии с использованием. Существует даже проблема дополнительного кода, сгенерированного компилятором, у которого даже есть решение (оптимизация количества предварительно скомпилированных библиотек). ссылка — J.Richter 4th editions, Generics, страница 314. В противном случае он никогда не будет таким же быстрым, как исходный не общий код, но равным отражению.

Ответ №2:

Разрешение перегрузки выполняется во время компиляции, а не во время выполнения.

Обычное решение — использовать здесь простую виртуальную отправку:

 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"
}
  

Комментарии:

1. Это работает, как ожидалось, но это не связано с Visitor. Это простая виртуальная отправка.

Ответ №3:

Элемент ВСЕГДА будет проверяться как родительский тип.

Идея, лежащая в основе общих кодов, заключается в том, что у вас нет кода, специфичного для конкретного типа.. отсюда и «общая» часть этого.

Вы ограничиваетесь ветвью классов, в данном случае родительским и всем, что от него происходит. Это означает, что код должен выполняться так, как если бы передаваемый объект имел родительский тип.

Как сказал dtb, здесь может применяться шаблон Visitor.

Другой идеей было бы просто иметь интерфейс, который определял бы метод Validate (), который должен был поддерживать каждый класс. Я думаю, что это был бы лучший путь.

Ответ №4:

Предполагая, что вы можете использовать C # 4.0, вы можете устранить статическое разрешение перегрузки, которое будет выполнять компилятор C #, используя ключевое слово «dynamic». Просто измените свою функцию validate на:

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

и результат будет:

 C:tmp>temp.exe
A
B
C

DONE
  

Я только что узнал об этом сам по ссылке @juharr на блог Эрика Липперта. Подробнее о динамическом типе читайте в msdn.