다음을 통해 공유


어셈블리 로드 모범 사례

비고

이 문서는 .NET Framework에만 적용됩니다. .NET 6 이상 버전을 포함하여 .NET의 최신 구현에는 적용되지 않습니다.

이 문서에서는 형식 ID와 기타 오류로 이어질 InvalidCastExceptionMissingMethodException수 있는 문제를 방지하는 방법에 대해 설명합니다. 이 문서에서는 다음 권장 사항을 설명합니다.

부하 컨텍스트의 장점과 단점을 이해하는 첫 번째 권장 사항은 모두 부하 컨텍스트에 대한 지식에 따라 달라지므로 다른 권장 사항에 대한 배경 정보를 제공합니다.

부하 컨텍스트의 장점 및 단점 이해

애플리케이션 도메인 내에서 어셈블리를 세 가지 컨텍스트 중 하나로 로드하거나 컨텍스트 없이 로드할 수 있습니다.

  • 기본 로드 컨텍스트에는 전역 어셈블리 캐시를 검색하여 찾은 어셈블리, 런타임이 호스트되는 경우 호스트 어셈블리 저장소(예: SQL Server) 및 ApplicationBasePrivateBinPath 애플리케이션 도메인이 포함됩니다. 대부분의 메서드 오버로드는 어셈블리를 Load 이 컨텍스트로 로드합니다.

  • 로드-프롬 컨텍스트에는 로더가 검색하지 않는 위치에서 로드된 어셈블리들이 포함됩니다. 예를 들어 추가 기능은 애플리케이션 경로 아래에 없는 디렉터리에 설치될 수 있습니다. Assembly.LoadFrom, AppDomain.CreateInstanceFrom경로 AppDomain.ExecuteAssembly 별로 로드하는 메서드의 예입니다.

  • 리플렉션 전용 컨텍스트는 ReflectionOnlyLoadReflectionOnlyLoadFrom 메서드를 사용하여 로드된 어셈블리를 포함합니다. 이 컨텍스트의 코드는 실행할 수 없으므로 여기서는 설명하지 않습니다. 자세한 내용은 방법: Reflection-Only 컨텍스트에 어셈블리 로드를 참조하세요.

  • 리플렉션 내보내기를 사용하여 임시 동적 어셈블리를 생성한 경우 어셈블리는 컨텍스트에 없습니다. 또한 메서드를 사용하여 LoadFile 로드되는 대부분의 어셈블리는 컨텍스트 없이 로드되며 바이트 배열에서 로드되는 어셈블리는 정책이 적용된 후 ID가 전역 어셈블리 캐시에 있음을 설정하지 않는 한 컨텍스트 없이 로드됩니다.

다음 섹션에서 설명한 대로 실행 컨텍스트에는 장점과 단점이 있습니다.

기본 로드 컨텍스트

어셈블리가 기본 로드 컨텍스트에 로드되면 해당 종속성이 자동으로 로드됩니다. 기본 로드 컨텍스트에 로드되는 종속성은 기본 로드 컨텍스트나 로드-프롬 컨텍스트의 어셈블리에 대해 자동으로 찾습니다. 어셈블리 ID로 로드하면 알 수 없는 버전의 어셈블리가 사용되지 않도록 하여 애플리케이션의 안정성이 향상됩니다( 부분 어셈블리 이름에 바인딩 방지 섹션 참조).

기본 로드 컨텍스트를 사용하면 다음과 같은 단점이 있습니다.

  • 다른 컨텍스트에 로드된 종속성은 사용할 수 없습니다.

  • 검색 경로 외부의 위치에서 기본 로드 컨텍스트로 어셈블리를 로드할 수 없습니다.

Load-From 컨텍스트

로드-프롬 컨텍스트를 사용하면 애플리케이션 경로에 포함되지 않은 경로에서 어셈블리를 로드할 수 있으며, 따라서 프로빙에 포함되지 않습니다. 경로 정보는 컨텍스트에서 유지 관리되므로 해당 경로에서 종속성을 찾아서 로드할 수 있습니다. 또한 이 컨텍스트의 어셈블리는 기본 로드 컨텍스트에 로드되는 종속성을 사용할 수 있습니다.

메서드 또는 경로별로 로드하는 다른 메서드 중 하나를 사용하여 Assembly.LoadFrom 어셈블리를 로드하는 경우 다음과 같은 단점이 있습니다.

  • 동일한 ID를 가진 어셈블리가 이미 load-from 컨텍스트 LoadFrom에 로드된 경우, 다른 경로가 지정되어도 이미 로드된 어셈블리를 반환합니다.

  • 어셈블리가 LoadFrom로 로드되는 경우, 나중에 기본 로드 컨텍스트에서 어셈블리를 표시 이름으로 동일한 어셈블리를 로드하려고 하면 로드 시도가 실패합니다. 어셈블리가 역직렬화될 때 발생할 수 있습니다.

  • 어셈블리가 LoadFrom으로 로드되고, 검색 경로에 동일한 ID를 가진 어셈블리가 다른 위치에 InvalidCastException 포함되어 있는 경우 MissingMethodException 또는 다른 예기치 않은 동작이 발생할 수 있습니다.

  • LoadFrom은/는 지정된 경로에서 FileIOPermissionAccess.ReadFileIOPermissionAccess.PathDiscovery를 요구하거나 WebPermission를 요구합니다.

  • 어셈블리에 대한 네이티브 이미지가 있는 경우 사용되지 않습니다.

  • 어셈블리를 도메인 중립으로 로드할 수 없습니다.

  • .NET Framework 버전 1.0 및 1.1에서는 정책이 적용되지 않습니다.

컨텍스트 없음

컨텍스트 없이 로드하는 것은 리플렉션 내보내기를 사용하여 생성되는 임시 어셈블리에 대한 유일한 옵션입니다. 컨텍스트 없이 로드하는 것은 동일한 ID를 가진 여러 어셈블리를 하나의 애플리케이션 도메인에 로드하는 유일한 방법입니다. 검색 비용은 피할 수 있습니다.

바이트 배열에서 로드되는 어셈블리는 정책이 적용될 때 설정된 어셈블리의 ID가 전역 어셈블리 캐시의 어셈블리 ID와 일치하지 않는 한 컨텍스트 없이 로드됩니다. 이 경우 어셈블리는 전역 어셈블리 캐시에서 로드됩니다.

컨텍스트 없이 어셈블리를 로드하는 경우 다음과 같은 단점이 있습니다.

  • 다른 어셈블리는 이벤트를 처리하지 않는 한 컨텍스트 없이 로드되는 어셈블리에 AppDomain.AssemblyResolve 바인딩할 수 없습니다.

  • 종속성은 자동으로 로드되지 않습니다. 컨텍스트 없이 미리 로드하거나, 기본 로드 컨텍스트로 미리 로드하거나, 이벤트를 처리하여 AppDomain.AssemblyResolve 로드할 수 있습니다.

  • 컨텍스트 없이 동일한 ID를 가진 여러 어셈블리를 로드하면 동일한 ID를 가진 어셈블리를 여러 컨텍스트로 로드하여 발생하는 것과 유사한 형식 ID 문제가 발생할 수 있습니다. 어셈블리를 여러 컨텍스트에 로드하지 않도록 합니다.

  • 어셈블리에 대한 네이티브 이미지가 있는 경우 사용되지 않습니다.

  • 어셈블리를 도메인 중립으로 로드할 수 없습니다.

  • .NET Framework 버전 1.0 및 1.1에서는 정책이 적용되지 않습니다.

부분 어셈블리 이름에 바인딩 방지

부분 이름 바인딩은 어셈블리를 로드할 때 어셈블리 표시 이름(FullName)의 일부만 지정할 때 발생합니다. 예를 들어 버전, 문화권 및 공개 키 토큰을 생략하여 어셈블리의 간단한 이름만 사용하여 메서드를 호출 Assembly.Load 할 수 있습니다. 또는 메서드를 Assembly.LoadWithPartialName 호출할 수 있습니다. 이 메서드는 먼저 메서드를 Assembly.Load 호출하고 어셈블리를 찾지 못하면 전역 어셈블리 캐시를 검색하고 사용 가능한 최신 버전의 어셈블리를 로드합니다.

부분 이름 바인딩은 다음을 포함하여 많은 문제를 일으킬 수 있습니다.

  • 메서드는 Assembly.LoadWithPartialName 동일한 간단한 이름의 다른 어셈블리를 로드할 수 있습니다. 예를 들어 두 애플리케이션은 둘 다 간단한 이름을 GraphicsLibrary 가진 두 개의 완전히 다른 어셈블리를 전역 어셈블리 캐시에 설치할 수 있습니다.

  • 실제로 로드된 어셈블리는 이전 버전과 호환되지 않을 수 있습니다. 예를 들어 버전을 지정하지 않으면 프로그램이 원래 사용하도록 작성된 버전보다 훨씬 이후 버전이 로드될 수 있습니다. 이후 버전을 변경하면 애플리케이션에서 오류가 발생할 수 있습니다.

  • 실제로 로드된 어셈블리는 미래 버전에 호환되지 않을 수 있습니다. 예를 들어 최신 버전의 어셈블리를 사용하여 애플리케이션을 빌드하고 테스트했을 수 있지만 부분 바인딩은 애플리케이션에서 사용하는 기능이 없는 훨씬 이전 버전을 로드할 수 있습니다.

  • 새 애플리케이션을 설치하면 기존 애플리케이션이 중단됩니다. 메서드 LoadWithPartialName를 사용하는 애플리케이션은 호환되지 않는 최신 버전의 공유 어셈블리를 설치하여 오작동할 수 있습니다.

  • 예기치 않은 종속성 로드가 발생할 수 있습니다. 종속성을 공유하는 두 어셈블리를 로드하면 부분 바인딩으로 로드하면 빌드되거나 테스트되지 않은 구성 요소를 사용하여 하나의 어셈블리가 발생할 수 있습니다.

발생할 수 있는 문제 때문에 메서드가 LoadWithPartialName 사용되지 않는 것으로 표시되었습니다. 대신 메서드를 Assembly.Load 사용하고 전체 어셈블리 표시 이름을 지정하는 것이 좋습니다. 부하 컨텍스트의 장점 및 단점 이해기본 부하 컨텍스트로 전환하는 것이 좋습니다.

LoadWithPartialName 메서드를 사용하면 어셈블리 로드가 쉬워지지만, 누락된 어셈블리를 식별하는 오류 메시지를 통해 애플리케이션이 실패하는 것이 알 수 없는 버전의 어셈블리를 자동으로 사용하는 것보다 더 나은 사용자 환경을 제공할 수 있다는 점을 고려해야 합니다. 왜냐하면, 알 수 없는 버전을 사용할 경우 예측할 수 없는 동작과 보안 허점이 발생할 수 있기 때문입니다.

어셈블리를 여러 컨텍스트로 로드하지 않도록 방지

어셈블리를 여러 컨텍스트로 로드하면 형식 ID 문제가 발생할 수 있습니다. 동일한 형식이 동일한 어셈블리에서 두 개의 서로 다른 컨텍스트로 로드되는 경우 이름이 같은 두 가지 형식이 로드된 것처럼 표시됩니다. InvalidCastException을(를) 다른 형식으로 캐스팅하려고 하면 형식 MyType을(를) 형식 MyType으로 캐스팅할 수 없다는 혼란스러운 메시지와 함께 예외가 발생합니다.

예를 들어, 인터페이스 ICommunicateUtility이라는 어셈블리에 선언되어 있으며, 이 어셈블리는 귀하의 프로그램과 귀하의 프로그램이 로드하는 다른 어셈블리에서도 참조됩니다. 이러한 다른 어셈블리에는 인터페이스를 구현하는 형식이 ICommunicate 포함되어 있으므로 프로그램에서 사용할 수 있습니다.

이제 프로그램이 실행되면 어떻게 되는지 고려합니다. 프로그램에서 참조하는 어셈블리는 기본 로드 컨텍스트로 로드됩니다. 해당 ID로 대상 어셈블리를 로드하는 경우 메서드를 Load 사용하여 기본 로드 컨텍스트에 있으며 종속성도 마찬가지입니다. 프로그램과 대상 어셈블리 모두 동일한 Utility 어셈블리를 사용합니다.

주어진 파일 경로를 이용하여 LoadFile 메서드를 사용해 대상 어셈블리를 로드한다고 가정해봅시다. 어셈블리는 컨텍스트 없이 로드되므로 해당 종속성이 자동으로 로드되지 않습니다. AppDomain.AssemblyResolve 이벤트에 대한 처리기가 있어 종속성을 제공할 수 있으며, 메서드 Utility를 사용하여 컨텍스트 없이 어셈블리 LoadFile를 로드할 수 있습니다. 이제 대상 어셈블리에 포함된 형식의 인스턴스를 생성하고 이를 ICommunicate 형식의 변수에 할당하려고 하면, 런타임이 두 개의 InvalidCastException 어셈블리 복사본에 있는 ICommunicate 인터페이스를 서로 다른 형식으로 간주하기 때문에 Utility 예외가 발생합니다.

어셈블리를 여러 컨텍스트로 로드할 수 있는 다른 많은 시나리오가 있습니다. 가장 좋은 방법은 애플리케이션 경로에서 대상 어셈블리를 재배치하고 전체 표시 이름으로 메서드를 Load 사용하여 충돌을 방지하는 것입니다. 그러면 어셈블리가 기본 로드 컨텍스트로 로드되고 두 어셈블리 모두 동일한 Utility 어셈블리를 사용합니다.

대상 어셈블리가 애플리케이션 경로 외부에 남아 있어야 하는 경우, LoadFrom 메서드를 사용하여 이를 로드-프롬 컨텍스트에 로드할 수 있습니다. 대상 어셈블리가 애플리케이션의 Utility 어셈블리에 대한 참조로 컴파일된 경우 애플리케이션이 기본 로드 컨텍스트로 로드한 어셈블리를 사용합니다 Utility . 대상 어셈블리가 애플리케이션 경로 외부에 있는 어셈블리의 Utility 복사본에 종속되어 있으면 문제가 발생할 수 있습니다. 애플리케이션이 Utility 어셈블리를 로드하기 전에 해당 어셈블리가 로드-프롬 컨텍스트에 로드되면 애플리케이션의 로드가 실패합니다.

기본 로드 컨텍스트로 전환 고려 섹션에서는 LoadFileLoadFrom와 같이 파일 경로 로드 대신 사용할 수 있는 대안에 대해 설명합니다.

여러 버전의 어셈블리를 동일한 컨텍스트로 로드하지 않도록 방지

여러 버전의 어셈블리를 하나의 로드 컨텍스트로 로드하면 형식 ID 문제가 발생할 수 있습니다. 동일한 어셈블리의 두 버전에서 동일한 형식이 로드되는 경우 이름이 같은 두 가지 형식이 로드된 것처럼 표시됩니다. InvalidCastException을(를) 다른 형식으로 캐스팅하려고 하면 형식 MyType을(를) 형식 MyType으로 캐스팅할 수 없다는 혼란스러운 메시지와 함께 예외가 발생합니다.

예를 들어 프로그램에서 한 버전의 어셈블리를 Utility 직접 로드할 수 있으며 나중에 다른 버전의 어셈블리를 로드하는 다른 어셈블리를 Utility 로드할 수 있습니다. 또는 코딩 오류로 인해 애플리케이션의 두 코드 경로가 서로 다른 버전의 어셈블리를 로드할 수 있습니다.

기본 로드 컨텍스트에서 이 문제는 메서드를 Assembly.Load 사용하고 다른 버전 번호를 포함하는 전체 어셈블리 표시 이름을 지정할 때 발생할 수 있습니다. 컨텍스트 없이 로드되는 어셈블리의 경우 메서드를 사용하여 Assembly.LoadFile 다른 경로에서 동일한 어셈블리를 로드하면 문제가 발생할 수 있습니다. 런타임은 ID가 같더라도 다른 경로에서 로드된 두 어셈블리를 서로 다른 어셈블리로 간주합니다.

형식 ID 문제 외에도, 어셈블리의 여러 버전 중 한 버전의 어셈블리에서 로드된 형식을 다른 버전의 형식을 예상하는 코드에 전달할 경우 MissingMethodException가 발생할 수 있습니다. 예를 들어 코드는 이후 버전에 추가된 메서드를 예상할 수 있습니다.

버전 간에 형식의 동작이 변경된 경우 더 미묘한 오류가 발생할 수 있습니다. 예를 들어 메서드가 예기치 않은 예외를 throw하거나 예기치 않은 값을 반환할 수 있습니다.

코드를 신중하게 검토하여 한 버전의 어셈블리만 로드되는지 확인합니다. 메서드를 AppDomain.GetAssemblies 사용하여 지정된 시간에 로드되는 어셈블리를 확인할 수 있습니다.

기본 로드 컨텍스트로 전환하는 것이 좋습니다.

애플리케이션의 어셈블리 로드 및 배포 패턴을 검사합니다. 바이트 배열에서 로드되는 어셈블리를 제거할 수 있나요? 어셈블리를 탐색 경로로 옮길 수 있나요? 어셈블리가 전역 어셈블리 캐시 또는 애플리케이션 도메인의 검색 경로(즉, ApplicationBasePrivateBinPath)에 있는 경우 해당 ID로 어셈블리를 로드할 수 있습니다.

모든 어셈블리를 검색 경로에 넣을 수 없는 경우 .NET Framework 추가 기능 모델 사용, 전역 어셈블리 캐시에 어셈블리 배치 또는 애플리케이션 도메인 만들기와 같은 대안을 고려합니다.

.NET Framework Add-In 모델 사용 고려

일반적으로 애플리케이션 기반에 설치되지 않은 추가 기능을 구현하기 위해 부하 원본 컨텍스트를 사용하는 경우 .NET Framework 추가 기능 모델을 사용합니다. 이 모델은 애플리케이션 도메인을 직접 관리할 필요 없이 애플리케이션 도메인 또는 프로세스 수준에서 격리를 제공합니다. 추가 기능 모델에 대한 자세한 내용은 추가 기능 및 확장성을 참조하세요.

글로벌 어셈블리 캐시 사용을 고려하십시오

기본 로드 컨텍스트의 이점을 잃거나 다른 컨텍스트의 단점을 감수하지 않고 애플리케이션 기반 외부에 있는 공유 어셈블리 경로의 이점을 얻기 위해 전역 어셈블리 캐시에 어셈블리를 배치합니다.

애플리케이션 도메인 사용 고려

일부 어셈블리를 애플리케이션의 검색 경로에 배포할 수 없는 경우 해당 어셈블리에 대한 새 애플리케이션 도메인을 만드는 것이 좋습니다. AppDomainSetup 새 애플리케이션 도메인을 만들고 속성을 사용하여 AppDomainSetup.ApplicationBase 로드할 어셈블리가 포함된 경로를 지정할 수 있습니다. 검색할 디렉터리가 여러 개 있는 경우 루트 디렉터리로 설정하고 ApplicationBase 속성을 사용하여 AppDomainSetup.PrivateBinPath 검색할 하위 디렉터리를 식별할 수 있습니다. 또는 여러 애플리케이션 도메인을 만들고 각 애플리케이션 도메인을 ApplicationBase 해당 어셈블리에 대한 적절한 경로로 설정할 수 있습니다.

Assembly.LoadFrom 메서드를 사용하여 이러한 어셈블리를 로드할 수 있습니다. 이제 탐색 경로에 있으므로 로드-프롬 컨텍스트 대신 기본 로드 컨텍스트로 로드됩니다. 그러나 올바른 버전이 항상 사용되도록 메서드로 Assembly.Load 전환하고 전체 어셈블리 표시 이름을 제공하는 것이 좋습니다.

참고하십시오