Udostępnij za pośrednictwem


Najlepsze rozwiązania dotyczące ładowania zestawów

Uwaga / Notatka

Ten artykuł jest specyficzny dla programu .NET Framework. Nie ma zastosowania do nowszych implementacji platformy .NET, w tym .NET 6 i nowszych wersji.

W tym artykule omówiono sposoby unikania problemów z tożsamością typu, które mogą prowadzić do błędów InvalidCastException, MissingMethodException i innych. W tym artykule omówiono następujące zalecenia:

Pierwsze zalecenie, zrozumienie zalet i wad kontekstów ładowania, zawiera podstawowe informacje dotyczące innych zaleceń, ponieważ wszystkie one zależą od wiedzy kontekstów ładowania.

Omówienie zalet i wad kontekstów ładowania

W domenie aplikacji zestawy można załadować do jednego z trzech kontekstów lub można je załadować bez kontekstu:

  • Domyślny kontekst ładowania zawiera zestawy znalezione przez sondowanie globalnej pamięci podręcznej zestawów, magazynu zestawów hosta, jeśli środowisko uruchomieniowe jest hostowane (na przykład w programie SQL Server), oraz ApplicationBase i PrivateBinPath domeny aplikacji. Większość przeciążeń metody ładuje zestawy Load do tego kontekstu.

  • Kontekst load-from zawiera zestawy ładowane z lokalizacji, które nie są przeszukiwane przez moduł ładujący. Na przykład dodatki mogą być zainstalowane w katalogu, który nie znajduje się w ścieżce aplikacji. Assembly.LoadFrom, AppDomain.CreateInstanceFromi AppDomain.ExecuteAssembly to przykłady metod, które ładują się według ścieżki.

  • Kontekst tylko do odbicia zawiera zestawy załadowane za pomocą metod ReflectionOnlyLoad i ReflectionOnlyLoadFrom. Nie można wykonać kodu w tym kontekście, więc nie omówiono go tutaj. Aby uzyskać więcej informacji, zobacz How to: Load Assemblies into the Reflection-Only Context (Instrukcje: ładowanie zestawów do kontekstu Reflection-Only).

  • Wygenerowany przez emisję odbicia przejściowy zestaw dynamiczny nie znajduje się w żadnym kontekście. Ponadto większość zestawów ładowanych przy użyciu metody LoadFile jest ładowana bez kontekstu, a ładowane z tablic bajtowych są ładowane bez kontekstu, chyba że ich tożsamość (po zastosowaniu zasad) ustala, że znajdują się w globalnej pamięci podręcznej zestawów (GAC).

Konteksty wykonywania mają zalety i wady, jak opisano w poniższych sekcjach.

Domyślny kontekst ładowania

Gdy zestawy są ładowane do domyślnego kontekstu ładowania, ich zależności są ładowane automatycznie. Zależności, które są ładowane do domyślnego kontekstu ładowania, są automatycznie znajdowane dla zestawów w domyślnym kontekście ładowania lub w kontekście ładowania typu load-from. Ładowanie według tożsamości zestawu zwiększa stabilność aplikacji, zapewniając, że nie są używane nieznane wersje zestawów (zobacz sekcję Unikanie wiązania częściowych nazw zestawów).

Użycie domyślnego kontekstu ładowania ma następujące wady:

  • Zależności, które są ładowane do innych kontekstów, nie są dostępne.

  • Nie można załadować zestawów z lokalizacji spoza ścieżki sondowania do domyślnego kontekstu ładowania.

Load-From kontekst

Kontekst load-from umożliwia załadowanie zestawu ze ścieżki, która nie znajduje się pod ścieżką aplikacji, i dlatego nie jest uwzględniana w sondowaniu. Umożliwia ona zlokalizowanie i załadowanie zależności z tej ścieżki, ponieważ informacje o ścieżce są utrzymywane przez kontekst. Ponadto zestawy w tym kontekście mogą używać zależności załadowanych do domyślnego kontekstu ładowania.

Ładowanie zestawów przy użyciu metody Assembly.LoadFrom lub jednej z innych metod, które ładują według ścieżki, ma następujące wady:

  • Jeśli zestaw z tą samą tożsamością jest już załadowany w kontekście ładowania-źródła, LoadFrom zwraca załadowany zestaw, nawet jeśli określono inną ścieżkę.

  • Jeśli zestaw jest ładowany z elementem LoadFrom, a później zestaw w domyślnym kontekście ładowania próbuje załadować ten sam zestaw według nazwy wyświetlanej, próba załadowania zakończy się niepowodzeniem. Taka sytuacja może wystąpić, gdy zestaw jest deserializowany.

  • Jeśli zestaw jest ładowany z LoadFrom, a ścieżka sondowania zawiera zestaw z tą samą tożsamością, ale w innej lokalizacji, może wystąpić InvalidCastException, MissingMethodException lub inne nieoczekiwane zachowanie.

  • LoadFrom żąda FileIOPermissionAccess.Read oraz FileIOPermissionAccess.PathDiscovery lub WebPermission, w określonej ścieżce.

  • Jeśli dla zestawu istnieje obraz macierzysty, nie jest on używany.

  • Nie można załadować zestawu jako neutralnego pod względem domeny.

  • W programie .NET Framework w wersjach 1.0 i 1.1 zasady nie są stosowane.

Brak kontekstu

Ładowanie bez kontekstu stanowi jedyną możliwość dla zestawów przejściowych generowanych z emisją odbicia. Ładowanie bez kontekstu jest jedynym sposobem ładowania wielu zestawów, które mają tę samą tożsamość w jednej domenie aplikacji. Unika się kosztów sondowania.

Zestawy ładowane z tablic bajtowych są ładowane bez kontekstu, chyba że tożsamość zestawu, która jest ustanawiana podczas stosowania zasad, pasuje do tożsamości zestawu w globalnej pamięci podręcznej zestawów; w takim przypadku zestaw jest ładowany z globalnej pamięci podręcznej zestawów.

Ładowanie zestawów bez kontekstu ma następujące wady:

  • Inne zespoły nie mogą się łączyć z zestawami ładowanymi bez kontekstu, chyba że zajmiesz się obsługą zdarzenia AppDomain.AssemblyResolve.

  • Zależności nie są ładowane automatycznie. Można je wstępnie załadować bez kontekstu, wstępnie załadować do domyślnego kontekstu ładowania lub załadować je, obsługując AppDomain.AssemblyResolve zdarzenie.

  • Ładowanie wielu zestawów z tą samą tożsamością bez kontekstu może spowodować problemy z tożsamością typu podobne do tych, które są spowodowane ładowaniem zestawów z tą samą tożsamością do wielu kontekstów. Zobacz Unikaj ładowania zestawu do wielu kontekstów.

  • Jeśli dla zestawu istnieje obraz macierzysty, nie jest on używany.

  • Nie można załadować zestawu jako neutralnego pod względem domeny.

  • W programie .NET Framework w wersjach 1.0 i 1.1 zasady nie są stosowane.

Unikaj wiązania w przypadku częściowych nazw zestawów

Powiązanie części nazwy występuje, gdy określasz tylko część pokazywanej nazwy zestawu (FullName) podczas ładowania zestawu. Można na przykład wywołać metodę Assembly.Load z tylko prostą nazwą zestawu, pomijając wersję, kulturę i token klucza publicznego. Możesz też wywołać metodę Assembly.LoadWithPartialName , która najpierw wywołuje Assembly.Load metodę i, jeśli nie uda się zlokalizować zestawu, przeszukuje globalną pamięć podręczną zestawów i ładuje najnowszą dostępną wersję zestawu.

Częściowe wiązanie nazw może powodować wiele problemów, w tym następujące:

  • Metoda Assembly.LoadWithPartialName może załadować inny zestaw o tej samej prostej nazwie. Na przykład dwie aplikacje mogą instalować dwa zupełnie różne zestawy, które mają prostą nazwę GraphicsLibrary w globalnej pamięci podręcznej zestawów.

  • Zestaw, który jest faktycznie załadowany, może nie być zgodny z poprzednimi wersjami. Na przykład nie określenie wersji może spowodować załadowanie znacznie nowszej wersji niż wersja, z jaką program został pierwotnie napisany do użycia. Zmiany w nowszej wersji mogą powodować błędy w aplikacji.

  • Zestaw, który jest faktycznie załadowany, może nie być zgodny w przód. Na przykład aplikacja mogła zostać skompilowana i przetestowana przy użyciu najnowszej wersji zestawu, ale częściowe powiązanie może załadować znacznie wcześniejszą wersję, która nie zawiera funkcji używanych przez aplikację.

  • Instalowanie nowych aplikacji może przerwać istniejące aplikacje. Aplikacja korzystająca z LoadWithPartialName metody może zostać uszkodzona przez zainstalowanie nowszej, niezgodnej wersji zestawu udostępnionego.

  • Może wystąpić nieoczekiwane ładowanie zależności. Jeśli załadujesz dwa zestawy współdzielące zależność, ich ładowanie z częściowym powiązaniem może skutkować tym, że jeden z zestawów użyje komponentu, z którym nie został zbudowany ani przetestowany.

Ze względu na problemy, które może spowodować, LoadWithPartialName metoda została oznaczona jako przestarzała. Zalecamy zamiast tego użycie Assembly.Load metody i określenie pełnych nazw wyświetlanych zestawów. Zobacz Omówienie zalet i wad kontekstów ładowania i rozważ przełączenie do domyślnego kontekstu ładowania.

Jeśli chcesz użyć LoadWithPartialName metody , ponieważ ułatwia ładowanie zestawu, rozważ, że niepowodzenie aplikacji z komunikatem o błędzie, który identyfikuje brakujący zestaw, może zapewnić lepsze środowisko użytkownika niż automatyczne użycie nieznanej wersji zestawu, co może spowodować nieprzewidywalne zachowanie i luki bezpieczeństwa.

Unikaj ładowania zestawu do wielu kontekstów

Ładowanie zestawu do wielu kontekstów może powodować problemy z jednoznacznością typów. Jeśli ten sam typ jest ładowany z tego samego zestawu do dwóch różnych kontekstów, jest tak, jakby załadowano dwa różne typy o tej samej nazwie. Jeśli spróbujesz rzutować jeden typ na inny, zgłaszany jest wyjątek InvalidCastException, z mylącym komunikatem, że nie można rzutować typu MyType na typ MyType.

Załóżmy na przykład, że interfejs ICommunicate jest zadeklarowany w zestawie o nazwie Utility, do którego odwołuje się zarówno twój program, jak i inne zestawy ładowane przez ten program. Te inne zestawy zawierają typy, które implementują ICommunicate interfejs, co pozwala programowi na ich użycie.

Teraz zastanów się, co się stanie po uruchomieniu programu. Zestawy, do których odwołuje się Twój program, są ładowane do domyślnego kontekstu ładowania. Jeśli załadujesz zestaw docelowy według jego tożsamości, używając metody Load, będzie on w domyślnym kontekście ładowania, podobnie jak jego zależności. Zarówno twój program, jak i zestaw docelowy będą używać tego samego zestawu Utility.

Załóżmy jednak, że załadujesz zestaw docelowy według jego ścieżki pliku przy użyciu LoadFile metody . Zestaw jest ładowany bez żadnego kontekstu, więc jego zależności nie są ładowane automatycznie. Może istnieć program obsługujący zdarzenia AppDomain.AssemblyResolve, który dostarcza zależność i może załadować bibliotekę Utility bez kontekstu, używając metody LoadFile. Teraz, gdy tworzysz instancję typu zawartego w docelowym zestawie i próbujesz przypisać ją do zmiennej typu ICommunicate, zostaje zgłoszony błąd InvalidCastException, ponieważ środowisko uruchomieniowe traktuje interfejsy ICommunicate w dwóch kopiach zestawu Utility jako różne typy.

Istnieje wiele innych scenariuszy, w których zestaw można załadować do wielu kontekstów. Najlepszym rozwiązaniem jest uniknięcie konfliktów przez przeniesienie zestawu docelowego w ścieżce aplikacji i użycie Load metody z pełną nazwą wyświetlaną. Zestaw jest następnie ładowany do domyślnego kontekstu ładowania, a oba zestawy korzystają z tego samego Utility zestawu.

Jeśli zestaw docelowy musi pozostać poza ścieżką aplikacji, możesz użyć metody LoadFrom w celu załadowania go do kontekstu ładowania. Jeśli zestaw docelowy został skompilowany z odwołaniem do zestawu aplikacji Utility, użyje zestawu Utility, który aplikacja załadowała do domyślnego kontekstu ładowania. Należy pamiętać, że problemy mogą wystąpić, jeśli zestaw docelowy ma zależność od kopii zestawu Utility znajdującej się poza ścieżką aplikacji. Jeśli dany zestaw zostanie załadowany do kontekstu ładowania przed załadowaniem zestawu Utility przez Twoją aplikację, ładowanie aplikacji zakończy się niepowodzeniem.

W sekcji Rozważ przełączenie do domyślnego kontekstu ładowania omówiono alternatywy dla ładowania ścieżek plików, takich jak LoadFile i LoadFrom.

Unikaj ładowania wielu wersji zestawu do tego samego kontekstu

Ładowanie wielu wersji zestawu do jednego kontekstu ładowania może powodować problemy z tożsamością typu. Jeśli ten sam typ jest ładowany z dwóch wersji tego samego zestawu, oznacza to, że załadowano dwa różne typy o tej samej nazwie. Jeśli spróbujesz rzutować jeden typ na inny, zgłaszany jest wyjątek InvalidCastException, z mylącym komunikatem, że nie można rzutować typu MyType na typ MyType.

Na przykład program może załadować jedną wersję Utility zestawu bezpośrednio, a później załadować inny zestaw, który ładuje inną wersję Utility zestawu. Lub błąd kodowania może spowodować, że w aplikacji różne ścieżki kodu załadują różne wersje zestawu.

W domyślnym kontekście ładowania ten problem może wystąpić w przypadku użycia Assembly.Load metody i określenia pełnych nazw wyświetlanych zestawów, które zawierają różne numery wersji. W przypadku zestawów, które są ładowane bez kontekstu, problem może być spowodowany użyciem Assembly.LoadFile metody ładowania tego samego zestawu z różnych ścieżek. Środowisko uruchomieniowe traktuje dwa zestawy ładowane z różnych ścieżek jako różne zestawy, nawet jeśli ich tożsamość jest taka sama.

Oprócz problemów z tożsamością typu, wiele wersji zestawu może spowodować MissingMethodException, jeśli typ załadowany z jednej wersji zestawu jest przekazywany do kodu, który oczekuje tego typu z innej wersji. Na przykład kod może oczekiwać metody, która została dodana do nowszej wersji.

Bardziej subtelne błędy mogą wystąpić, jeśli zachowanie typu zmieniło się między wersjami. Na przykład metoda może zgłosić nieoczekiwany wyjątek lub zwrócić nieoczekiwaną wartość.

Dokładnie przejrzyj kod, aby upewnić się, że załadowano tylko jedną wersję zestawu. Za pomocą AppDomain.GetAssemblies metody można określić, które zestawy są ładowane w danym momencie.

Rozważ przełączenie się do domyślnego kontekstu ładowania

Sprawdź wzorce ładowania i wdrażania zestawu aplikacji. Czy można wyeliminować zestawy ładowane z tablic bajtowych? Czy można przenieść zestawy do ścieżki sondowania? Jeśli zestawy umieszczone są w globalnej pamięci podręcznej zestawów lub w ścieżce wyszukiwania domeny aplikacji (czyli jej ApplicationBase i PrivateBinPath), można załadować zestaw na podstawie jego tożsamości.

Jeśli nie można umieścić wszystkich zestawów w ścieżce sondowania, rozważ alternatywy, takie jak użycie modelu dodatku .NET Framework, umieszczenie zestawów w globalnej pamięci podręcznej zestawów lub utworzenie domen aplikacji.

Rozważ użycie modelu Add-In programu .NET Framework

Jeśli używasz kontekstu ładowania do implementowania dodatków, które zwykle nie są zainstalowane w bazie aplikacji, użyj modelu dodatków .NET Framework. Ten model zapewnia izolację na poziomie domeny aplikacji lub procesu bez konieczności samodzielnego zarządzania domenami aplikacji. Aby uzyskać informacje na temat modelu dodatku, zobacz Dodatki i rozszerzalność.

Rozważ użycie globalnej pamięci podręcznej zestawów

Umieść zestawy w globalnej pamięci zestawów, aby uzyskać korzyści wynikające ze współdzielonej ścieżki dostępu do zestawów znajdującej się poza bazą aplikacji, bez tracenia zalet domyślnego kontekstu ładowania i unikania wad innych kontekstów.

Rozważ użycie domen aplikacji

Jeśli ustalisz, że niektórych zestawów nie można wdrożyć w ścieżce sondowania aplikacji, rozważ utworzenie nowej domeny aplikacji dla tych zestawów. Użyj elementu AppDomainSetup , aby utworzyć nową domenę aplikacji i użyć AppDomainSetup.ApplicationBase właściwości , aby określić ścieżkę zawierającą zestawy, które chcesz załadować. Jeśli masz wiele katalogów do przeszukiwania, ustaw ApplicationBase na katalog główny, a za pomocą właściwości AppDomainSetup.PrivateBinPath zidentyfikuj podkatalogi do przeszukiwania. Alternatywnie można utworzyć wiele domen aplikacji i ustawić ApplicationBase każdą domenę aplikacji na odpowiednią ścieżkę dla jej zestawów.

Pamiętaj, że możesz użyć Assembly.LoadFrom metody , aby załadować te zestawy. Ponieważ znajdują się one teraz w ścieżce sondowania, zostaną załadowane do domyślnego kontekstu ładowania zamiast ładowania z kontekstu. Zalecamy jednak przełączenie się do Assembly.Load metody i podanie pełnych nazw wyświetlanych zestawów w celu zapewnienia, że prawidłowe wersje są zawsze używane.

Zobacz także