2012-07-11 10 views
10

Myślałem, że Generics w C# zostały zaimplementowane w taki sposób, że nowa klasa/metoda/what-have-you została wygenerowana, albo w czasie wykonywania, albo podczas kompilacji, kiedy użyto nowego generycznego typu, podobnego do szablonów C++ (do którego nigdy nie zaglądałem i bardzo dobrze mogę się mylić, o co chętnie przyjmuję poprawki).W jaki sposób zaimplementowano C# Generics?

Ale w moim kodowania wymyśliłem dokładnego kontrprzykład:

static class Program { 
    static void Main() 
    { 
     Test testVar = new Test(); 

     GenericTest<Test> genericTest = new GenericTest<Test>(); 
     int gen = genericTest.Get(testVar); 

     RegularTest regTest = new RegularTest(); 
     int reg = regTest.Get(testVar); 

     if (gen == ((object)testVar).GetHashCode()) 
     { 
      Console.WriteLine("Got Object's hashcode from GenericTest!"); 
     } 
     if (reg == testVar.GetHashCode()) 
     { 
      Console.WriteLine("Got Test's hashcode from RegularTest!"); 
     } 
    } 

    class Test 
    { 
     public new int GetHashCode() 
     { 
      return 0; 
     } 
    } 

    class GenericTest<T> 
    { 
     public int Get(T obj) 
     { 
      return obj.GetHashCode(); 
     } 
    } 

    class RegularTest 
    { 
     public int Get(Test obj) 
     { 
      return obj.GetHashCode(); 
     } 
    } 
} 

Obie te linie konsoli druku.

Wiem, że faktycznym powodem tego jest to, że wirtualne wywołanie Object.GetHashCode() nie jest przetwarzane na Test.GetHashCode(), ponieważ metoda w teście jest oznaczona jako nowa zamiast przesłonięcia. Dlatego wiem, że jeśli użyłbym "override" zamiast "new" w Test.GetHashCode(), to powrót 0 przesłoni polimorficznie metodę GetHashCode w obiekcie i to nie będzie prawdą, ale zgodnie z moim (poprzednim) zrozumieniem generycznych C# nie miałoby znaczenia, ponieważ każda instancja T zostałaby zastąpiona przez Test, a zatem wywołanie metody miałoby statyczny (lub ogólny czas rozwiązania) został rozwiązany do "nowej" metody.

Moje pytanie brzmi następująco: W jaki sposób są realizowane generics w C#? Nie znam kodu bajtowego CIL, ale znam kod bajtowy Java, więc rozumiem, jak języki CLI zorientowane obiektowo działają na niskim poziomie. Możesz wyjaśnić na tym poziomie.

Odkładając na bok, myślałem, że generics C# zostało zaimplementowane w ten sposób, ponieważ każdy zawsze nazywa system ogólny w C# "True Generics" w porównaniu do systemu usuwania typu Java.

+0

dowolny powód do rzucenia do obiektu tutaj "gen == ((obiekt) testVar) .GetHashCode()"? – AlwaysAProgrammer

+0

Chociaż nie odpowiada bezpośrednio na twoje pytanie, http://blogs.msdn.com/b/ericlippert/archive/2012/07/10/when-is-a-cast-not-a-cast.aspx ma kilka dobrych informacje o tym, jak generics są rzutowane i jak odnoszą się do siebie w języku C#. – devstruck

+0

@Yogendra Wykonuje to dostęp do metody Object.GetHashCode() zamiast "nowej" metody Test.GetHashCode(). Dlatego zwraca inną wartość (ponieważ całkowicie działa inna metoda). – Carrotman42

Odpowiedz

7

W GenericTest<T>.Get(T), kompilator C# ma już wybrał, że należy wywołać object.GetHashCode (wirtualnie). W żaden sposób nie rozwiąże to metody "nowej" GetHashCode w czasie wykonywania (która będzie miała własny slot w tabeli metod, zamiast przesłonić slot dla object.GetHashCode).

Od Eric Lippert na What's the difference, part one: Generics are not templates, problem jest wyjaśnione (konfiguracja używana jest nieco inna, ale lekcje tłumaczyć dobrze do scenariusza):

To pokazuje, że leki generyczne w C# nie są takie jak szablony w C++. Możesz myśleć o szablonach jako o mechanizmach wyszukiwania i zastępowania fantazyjnych . [...] Nie jest tak, jak działają typy ogólne; typy ogólne to: well, generic. Wykonujemy przeciążenie o rozdzielczości po i pieczemy w wyniku . [...] IL, które wygenerowaliśmy dla rodzaju generycznego, ma już metodę, którą zamierza wywołać. Jitter nie mówi: "Cóż, zdaję sobie sprawę, że gdybyśmy teraz poprosili kompilator C# o wykonanie z tymi dodatkowymi informacjami, to wybrałoby to inne przeciążenie. Pozwól mi przepisać wygenerowany kod, aby zignorować kod wygenerowany pierwotnie przez kompilator C# ... "Jitter zna nic na temat reguł C#.

I obejście żądanych semantyki:

Teraz, jeśli chcemy rozdzielczości przeciążenie powinien być ponownie wykonywane w czasie wykonywania na podstawie typów runtime argumentów, możemy zrobić dla ty; to właśnie robi nowa "dynamiczna" funkcja w języku C# 4.0. Po prostu zamień "obiekt" na "dynamiczny", a gdy wykonasz wywołanie tego obiektu, wykonamy przeciążenie algorytmu rozdzielczości w czasie wykonywania i dynamicznie plujowy kod, który wywołuje metodę, którą wybrałby kompilator, gdyby był znany. wszystkie typy środowiska wykonawczego w czasie kompilacji.

+3

Ah Eric, cokolwiek byśmy zrobili bez ciebie. – Servy

+0

Tak, powiedziałem, że w akapicie zaczynającym się od "Wiem, że faktycznym powodem tego jest ..." Moje pytanie brzmiało "W jaki sposób zaimplementowano C# Generics?" Przeczytam link, który wysłałeś, może to odpowie na moje pytanie. – Carrotman42

+0

@ twoja edycja: To nie jest tak, że chcę to zrobić: Pochodzę z tła Java, gdzie ta cała "nowa metoda" nie istnieje. Myślę, że jest raczej podatny na błędy użytkownika i nie planuję używać go celowo. Przyczyną, na którą wpadłem, było to, że pisałem abstrakcyjną klasę, w której chciałem wymusić podklasy, aby zaimplementować GetHashCode, więc napisałem "public abstract int GetHashcode();", nie zdając sobie sprawy, że aby mieć tę pracę z niczym generalnie nazywając GetHashCode musiałem faktycznie powiedzieć "publiczny nadpisać streszczenie int GetHashcode();", który jest strasznie gadatliwy dla moich gustów. – Carrotman42

Powiązane problemy