2012-02-29 13 views

Odpowiedz

39

W przeszłości w środowisku .NET występowało napięcie między chęcią zautomatyzowania niektórych funkcji za pomocą refleksji i ich dostosowywania. Na przykład weź panel Właściwości w programie Visual Studio - w scenariuszach, w których pokazuje on pewien typ .NET (np. Kontrolkę na powierzchni projektu), może automatycznie wykryć i wyświetlić każdą publiczną właściwość zdefiniowaną przez typ.

Używanie odbicia dla tego rodzaju zachowań sterowanych typem jest użyteczne, ponieważ oznacza, że ​​każda właściwość pojawi się, a programista nie będzie musiał nic robić. Ale przedstawia problem: co zrobić, jeśli chcesz dostosować elementy, np. definiowanie kategoryzacji lub niestandardowego interfejsu edytującego dla konkretnej właściwości?

Klasycznym rozwiązaniem w .NET jest uderzenie paczką niestandardowych atrybutów w odpowiednich członków. Jednak jednym z problemów jest to, że może to oznaczać części kodu, które wykonują sensowne zadanie w czasie wykonywania, w zależności od klas, które wykonują tylko cokolwiek w czasie projektowania - poleganie na atrybutach uniemożliwia rozdzielenie aspektów czasu wykonywania i projektowania . Czy na pewno chcesz wysłać kod dla niestandardowego interfejsu użytkownika projektanta dla panelu właściwości VS jako części biblioteki kontrolnej, która znajdzie się na komputerach użytkowników końcowych?

Innym problemem jest to, że w niektórych sytuacjach warto zdecydować dynamicznie, jakie "właściwości" prezentujesz. Jednym z najstarszych tego przykładów (z domeny .NET 1.0) było umieszczenie DataSet w pewnego rodzaju kontrolce siatki (po stronie klienta lub sieci). W przypadku silnie wpisanego zestawu danych odbicie może być odpowiednim sposobem dla siatki do wykrycia właściwości dostarczanych przez źródło, ale można też użyć dynamicznie, więc potrzebujesz sposobu, aby siatka danych zapytała w czasie wykonywania, jakie kolumny wyświetlić.

(Jedna odpowiedź na to: zaprojektuj poprawnie interfejs użytkownika Generowanie siatki bezpośrednio w ten sposób prowadzi do okropnych wrażeń użytkownika, jednak wiele osób chce to zrobić leniwie, bez względu na to, czy to dobry pomysł, czy nie. .)

Tak więc masz sytuację, w której czasami potrzebujesz zachowania opartego na odbiciu, ale czasami chcesz mieć pełną kontrolę w czasie wykonywania.

Pojawiły się różne rozwiązania ad hoc. Masz całą rodzinę typów TypeDescriptor i PropertyDescriptor, które zapewniają rodzaj wirtualizującego widoku nad odbiciem. Domyślnie po prostu przekazałoby wszystko prosto od refleksji, ale typy mają możliwość włączenia dostarczania niestandardowych deskryptorów w środowisku wykonawczym, umożliwiając im modyfikację, a nawet całkowite zastąpienie ich wyglądu. ICustomTypeDescriptor jest częścią tego świata.

Zapewnia to domyślnie jedno rozwiązanie problemu zachowania opartego na refleksji z opcją zapewnienia zachowania sterowanego przez środowisko wykonawcze, jeśli jest to potrzebne. Ale nie rozwiązuje problemu, gdy chcesz to zrobić tylko w czasie projektowania, i nie chcesz wysyłać tego kodu jako części składowych redystrybucyjnych środowiska wykonawczego.

Tak więc kilka lat temu Visual Studio wprowadziło własne mechanizmy ad hoc do rozszerzania informacji o typie w czasie projektowania.Istnieje szereg rozwiązań opartych na konwencjach, w których program Visual Studio automatycznie wykrywa komponenty czasu projektowania związane z poszczególnymi komponentami środowiska wykonawczego, umożliwiając dostosowanie wyglądu projektu bez konieczności pieczenia odpowiedniego kodu w swoich składnikach redystrybucyjnych. Blend korzysta również z tego mechanizmu, chociaż z kilkoma zmianami, umożliwiającymi dostarczanie różnych elementów projektowych dla VS i Blend.

Oczywiście, żaden z nich nie jest widoczny przez interfejsy API do normalnego odbijania - VS i Blend mają warstwę opakowania, która znajduje się na górze odbicia, aby wszystko działało.

Więc teraz mamy dwie warstwy wirtualizacji, które mogą przejść przez do refleksji, czy mogą wzmacniać to, co wychodzi z refleksji ...

to wygląda w .NET 4.5, CLR zespół zdecydował, że skoro różne grupy już robiły tego typu rzeczy, a inne grupy chciały zrobić więcej (zespół MEF miał podobne wymagania do napędzanego odbiciem z opcjonalnym-rozszerzeniem działania), to było dokładnie to, co powinno być wbudowane w środowisko wykonawcze.

Nowy model wygląda tak: klasa bazowa ReflectionContext to abstrakcyjne API, dzięki któremu można uzyskać zwirtualizowaną wersję interfejsu API do refleksji. Jest zwodniczo prosty, ponieważ jednym z głównych pomysłów jest to, że nie potrzebujesz już wyspecjalizowanych API, takich jak system deskryptorów typów, jeśli Twoim jedynym celem jest uzyskanie zwirtualizowalnego opakowania na zasadzie odbicia - odbicie jest teraz wirtualizowalne po wyjęciu z pudełka. Więc można pisać takie rzeczy

public static void ShowAllAttributes(Type t) 
{ 
    foreach (Attribute attr in t.GetCustomAttributes(true)) 
    { 
     Console.WriteLine(attr); 
    } 
} 

Teraz zawsze byłem w stanie napisać, ale przed .NET 4.5, kod tak by zawsze pracować na „prawdziwym” informacji typu, ponieważ używa Reflection . Ale dzięki kontekstom refleksji możliwe jest teraz udostępnienie zwirtualizowanej wersji Type. Więc rozważyć tego typu bardzo nudne:

class NoRealAttributes 
{ 
} 

Jeśli po prostu przejść typeof(NoRealAttributes) do mojego sposobu ShowAllAttributes będzie wydrukować niczego. Ale mogę napisać (nieco contrived) kontekst zwyczaj refleksji:

class MyReflectionContext : CustomReflectionContext 
{ 
    protected override IEnumerable<object> GetCustomAttributes(MemberInfo member, IEnumerable<object> declaredAttributes) 
    { 
     if (member == typeof(NoRealAttributes)) 
     { 
      return new[] { new DefaultMemberAttribute("Foo") }; 
     } 
     else 
     { 
      return base.GetCustomAttributes(member, declaredAttributes); 
     } 
    } 
} 

(Nawiasem mówiąc, myślę, że rozróżnienie między CustomReflectionContext a jej podstawą, ReflectionContext jest to, że ten ostatni określa API dla wirtualizowalnej kontekście refleksji, natomiast CustomReflectionContext dodaje kilka pomocników, aby łatwiej można zaimplementować coś takiego), a teraz mogę używać, aby dostarczać wirtualnym wersję Type dla mojej klasy.

var ctx = new MyReflectionContext(); 
Type mapped = ctx.MapType(typeof(NoRealAttributes).GetTypeInfo()); 
ShowAllAttributes(mapped); 

w tym kodzie, mapped nadal odnosi się do obiekt Type , więc wszystko, co wie, jak korzystać z interfejsu API do refleksji, będzie mogło z nim pracować, ale teraz zgłosi obecność atrybutu, który w rzeczywistości nie istnieje. Oczywiście, Type jest abstrakcyjna, więc zawsze mamy coś, co się z tego wywodzi, a jeśli zadzwonisz pod numer mapped.GetType(), zobaczysz, że jest to raczej System.Reflection.Context.Custom.CustomType, niż zwykle widziana System.RuntimeType. I ten obiekt CustomType należy do mojego niestandardowego kontekstu, więc wszelkie inne obiekty interfejsu API, które uzyskasz za jego pośrednictwem (np. Jeśli napisałeś mapped.Assembly.GetTypes()), dostaniesz także dostosowane obiekty, które przechodzą przez mój niestandardowy kontekst, który miałby możliwość zmodyfikuj wszystko, co wyjdzie.

Kod umożliwia nawigację po systemie typów przy użyciu dostosowanego obiektu Type.Mimo że taki kod używa zwykłego API podstawowego odbicia, teraz mam możliwość spersonalizowania wszystkiego, co z tego wyniknie, jeśli uznaję to za stosowne.

Ten wirtualny widok jest dostępny tylko, jeśli o to poprosisz. Na przykład MEF w .NET 4.5 szuka niestandardowego atrybutu określającego, że powinien używać niestandardowego kontekstu odbicia użytkownika, ale w przeciwnym razie powróci do zwykłej refleksji. (A w przypadku mojej metody ShowAllAttributes używa dowolnego obiektu, który wybiorę, aby przekazać - nie wie, czy otrzymuje wirtualny lub "prawdziwy" obiekt typu.)

Krótko mówiąc, oznacza to nie potrzebujesz już ad hoc wrapperów wokół interfejsu API Reflect, jeśli potrzebujesz informacji o typach zwirtualizowanych.

+0

To jest świetna odpowiedź, dziękuję bardzo. Naprawdę miałem nadzieję, że pozwolą na wirtualizację jakiegokolwiek istniejącego wywołania refleksyjnego (które pomogłoby w wielu niepotrzebnych ograniczeniach opartych na refleksach w istniejących bibliotekach MS i bibliotekach innych niż MS). Jest to po prostu wbudowany pomocnik dla tego, co już możemy zrobić. Cóż, może się to okazać przydatne. –

+0

Ile punktów mogę ewentualnie przyznać? Z jednej strony spowodowałeś, że zauważyłem, że ta dziwna rzecz, przez którą się potknąłem (przez trałowanie Reflectora) jest w rzeczywistości nowością w stosunku do 4.5, a nie jednym z tych porzuconych gruchotników, że .NET ma tak wiele (wspomniałeś o kilku). Szczęście! Jeśli mogę zwirtualizować właściwość na typ w locie (zgodnie z obietnicą w dokumencie) i mieć je widoczne w VS intellisense, będę płakać z radości. Nieważne, że cały czas zmarnowałem na nieudanych próbach 1 001 (ostatnia i chyba najgorsza była próba pełnej podklasy konstelacji "System.Type"). Woo-hoo off, aby spróbować tego ... –

Powiązane problemy