참조 된 모든 어셈블리를 앱 도메인에 강제로로드하는 방법이 있습니까?


83

내 프로젝트는 다음과 같이 설정됩니다.

  • 프로젝트 "정의"
  • 프로젝트 구현"
  • 프로젝트 "소비자"

"Consumer"프로젝트는 "Definition"과 "Implementation"을 모두 참조하지만 "Implementation"의 어떤 유형도 정적으로 참조하지 않습니다.

애플리케이션이 시작되면 프로젝트 "Consumer"는 "구현"에서 유형을 찾아야하는 "정의"에서 정적 메소드를 호출합니다.

경로 나 이름을 모르고, 가급적이면 본격적인 IOC 프레임 워크를 사용하지 않고도 참조 된 어셈블리를 앱 도메인에 강제로로드 할 수있는 방법이 있습니까?


1
어떤 종류의 문제가 발생합니까? 하중을 강제해야하는 이유는 무엇입니까?
— Mike Two

정적 의존성이 없다 아마도 때문에, 모든로드하기 아니에요
— 다니엘 셰퍼을

구현에서 "유형 찾기"를 어떻게 시도하고 있습니까? 특정 인터페이스를 구현하는 것을 찾고 있습니까?
— Mike Two

2
@ 마이크 : 네. AppDomain.CurrentDomain.GetAssemblies를 수행하고 있으며 linq 쿼리를 사용하여 각각에 대해 GetTypes ()를 재귀 적으로 호출합니다.
— Daniel Schaffer

답변:


90

이것은 트릭을 수행하는 것처럼 보였습니다.

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
var loadedPaths = loadedAssemblies.Select(a => a.Location).ToArray();

var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();

toLoad.ForEach(path => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));

Jon이 언급했듯이 이상적인 솔루션은로드 된 각 어셈블리에 대한 종속성으로 재귀해야하지만 특정 시나리오에서는 걱정할 필요가 없습니다.


업데이트 : .NET 4에 포함 된 Managed Extensibility Framework (System.ComponentModel)는 이와 같은 작업을 수행하는 데 훨씬 더 나은 기능을 제공합니다.


5
이것은 나를 위해 작동하지 않습니다,로드되지 않은 참조 된 어셈블리는 AppDomain.CurrentDomain.GetAssemblies () ..에 표시되지 않습니다. 흠 ...
— Ted

11
어떤 시설? 검색을 통해 아무것도 찾지 못했습니다.
— nuzzolilo 2014

8
MEF를 사용하면 위의 코드를 다음과 같이 줄일 수 있습니다 new DirectoryCatalog(".");(참조 필요 System.ComponentModel.Composition).
— Allon Guralnek

1
이 대답은 내 문제를 해결하고 나를 위해 일했습니다. 내 어셈블리 중 다른 하나를 참조하는 MS 단위 테스트 프로젝트가 있었고 AppDomain.CurrentDomain.GetAssemblies ()는 테스트를 실행할 때 해당 어셈블리를 반환하지 않았습니다. 내 단위 테스트가 해당 라이브러리의 코드를 사용하고 있었음에도 불구하고 어셈블리가 일반 실행과 비교하여 MS 단위 테스트 프로젝트 (클래스 라이브러리)를로드하는 방식 때문에 "GetAssemblies"에 표시되지 않았을 수 있습니다. .exe 응용 프로그램. 코드가 리플렉션을 사용하고 단위 테스트에 실패하는 경우 염두에 두어야 할 사항입니다.
— Dean Lunz

4
추가하려는 경우 동적으로로드 된 어셈블리에주의하십시오. 호출 된 멤버는 동적 어셈블리에서 지원되지 않습니다. IsDynamic = false 인 어셈블리를 필터링하거나로드에 대해 내결함성이있을 수있는 경우 CurrentDomain.Load에 대한 호출을 시도 / 캐치합니다. 그리고 assembly.Location. 그것도 확인해야합니다. IsDynamic어셈블리 에는 작동하지 않습니다 .
— 엘리 Gassert

63

를 사용 Assembly.GetReferencedAssemblies하여를 얻은 AssemblyName[]다음 Assembly.Load(AssemblyName)각각 을 호출 할 수 있습니다. 물론 반복해야하지만 이미로드 한 어셈블리를 추적하는 것이 좋습니다. :)


나는 그것을 발견했지만 문제는 참조 된 어셈블리에서 내가하고있는 모든 작업을 수행해야한다는 것입니다 ... 최소한 단위 테스트 컨텍스트에서 GetCallingAssembly, GetExecutingAssembly는 참조 된 어셈블리를 반환하고 GetEntryAssembly는 null을 반환합니다. : \
— Daniel Schaffer

4
참조 어셈블리를로드 한 후 위의 방법으로 문제를 해결할 수 있습니다. 도움이된다면 특정 typeof (T) .Assembly를 요청할 수도 있습니다. 필요한 것은 구현 (참조되지 않음)이 포함 된 어셈블리를 동적으로로드하는 것입니다. 이 경우 이름의 정적 목록을 유지하고 수동으로로드하거나 전체 디렉토리를 통해로드 한 다음 올바른 인터페이스가있는 유형을 찾아야합니다.
— Fadrian Sudaman 2010 년

1
@vanhelgen : 내 경험상 명시 적으로 필요한 것은 거의 없습니다. 일반적으로 CLR의 "요청시로드"가 제대로 작동합니다.
— Jon Skeet

2
사실 일 수 있지만 DI 컨테이너를 사용하여 사용 가능한 서비스를 검색 할 때 (를 통해 System.Reflection) 자연스럽게 아직로드되지 않은 어셈블리 내에 포함 된 서비스를 찾지 못합니다. 그 이후로 기본 접근 방식은 모든 종속성이 제자리에 있는지 확인하기 위해 내 앱의 CompositionRoot에있는 모든 참조 된 어셈블리의 임의 유형에서 더미 하위 클래스를 만드는 것이 었습니다. 시작 시간이 더 늘어나더라도 모든 것을 미리로드하여이 말도 안되는 말을 건너 뛸 수 있기를 바랍니다. @ JonSkeet이 작업을 수행하는 다른 방법이 있습니까? thx
— mfeineis

12
이것이 실패하는 부분은 GetReferencedAssemblies가 "최적화 된"목록을 반환한다는 것입니다. 따라서 참조 된 어셈블리에서 코드를 명시 적으로 호출하지 않으면 포함되지 않습니다. (당 이 토론 )
— FTWinston

23

재귀 예제를 공유하고 싶었습니다. 다음과 같이 시작 루틴에서 LoadReferencedAssembly 메서드를 호출합니다.

foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
    this.LoadReferencedAssembly(assembly);
}

다음은 재귀 적 방법입니다.

private void LoadReferencedAssembly(Assembly assembly)
{
    foreach (AssemblyName name in assembly.GetReferencedAssemblies())
    {
        if (!AppDomain.CurrentDomain.GetAssemblies().Any(a => a.FullName == name.FullName))
        {
            this.LoadReferencedAssembly(Assembly.Load(name));
        }
    }
}

5
순환 어셈블리 참조로 인해 스택 오버플로 예외가 발생할 수 있는지 궁금합니다.
— Ronnie

1
Ronnie는 그렇지 않다고 생각합니다. 코드 name는 아직에 있지 않은 경우에만 재귀를 실행합니다 AppDomain.CurrentDomain.GetAssemblies(). 즉, foreach선택한 항목 AssemblyName이 아직로드되지 않은 경우에만 반복됩니다 .
— Felype 2017 년

1
나는에 대한 행복하지 않다 O(n^2)이 알고리즘 (의 런타임 GetAssemblies().Any(...)내부 foreach)). 나는를 사용하십시오 HashSet순서에 뭔가이 아래로 가져에 O(n).
— Dai

16

Fody.Costura 또는 기타 어셈블리 병합 솔루션을 사용하는 경우 허용되는 답변이 작동하지 않습니다.

다음은 현재로드 된 어셈블리의 참조 된 어셈블리를로드합니다. 재귀는 당신에게 맡겨져 있습니다.

var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();

loadedAssemblies
    .SelectMany(x => x.GetReferencedAssemblies())
    .Distinct()
    .Where(y => loadedAssemblies.Any((a) => a.FullName == y.FullName) == false)
    .ToList()
    .ForEach(x => loadedAssemblies.Add(AppDomain.CurrentDomain.Load(x)));

이 스 니펫이 어디로 가야하는지 알려주시겠습니까?
— Telemat 2014-06-12

1
당신의 부트 로더 / 시동에서 상상합니다.
— Meirion Hughes 2014-06-12

1
내가 틀렸을 수도 있지만 확인하실 수 있다고 생각 !y.IsDynamic합니다.Where
— Felype

1

오늘 특정 경로에서 어셈블리 + 종속성을로드해야했기 때문에이 클래스를 작성했습니다.

public static class AssemblyLoader
{
    private static readonly ConcurrentDictionary<string, bool> AssemblyDirectories = new ConcurrentDictionary<string, bool>();

    static AssemblyLoader()
    {
        AssemblyDirectories[GetExecutingAssemblyDirectory()] = true;
        AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;

    }

    public static Assembly LoadWithDependencies(string assemblyPath)
    {
        AssemblyDirectories[Path.GetDirectoryName(assemblyPath)] = true;
        return Assembly.LoadFile(assemblyPath);
    }

    private static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
    {
        string dependentAssemblyName = args.Name.Split(',')[0] + ".dll";
        List<string> directoriesToScan = AssemblyDirectories.Keys.ToList();

        foreach (string directoryToScan in directoriesToScan)
        {
            string dependentAssemblyPath = Path.Combine(directoryToScan, dependentAssemblyName);
            if (File.Exists(dependentAssemblyPath))
                return LoadWithDependencies(dependentAssemblyPath);
        }
        return null;
    }

    private static string GetExecutingAssemblyDirectory()
    {
        string codeBase = Assembly.GetExecutingAssembly().CodeBase;
        var uri = new UriBuilder(codeBase);
        string path = Uri.UnescapeDataString(uri.Path);
        return Path.GetDirectoryName(path);
    }
}

1
좋은 코드는 사전이 필요하지 않다는 점을 제외하고는 간단한 목록으로 충분합니다. 원본 코드가로드 된 어셈블리와로드되지 않은 어셈블리를 알아야 할 필요가 있다고 가정합니다. 이것이 바로 사전이있는 이유입니다.
— Reinis

0

또 다른 버전 ( Daniel Schaffer 답변 기반 )은 모든 어셈블리를로드 할 필요가 없지만 미리 정의 된 수의 어셈블리를로드 할 수있는 경우입니다.

var assembliesToLoad = { "MY_SLN.PROJECT_1", "MY_SLN.PROJECT_2" };

// First trying to get all in above list, however this might not 
// load all of them, because CLR will exclude the ones 
// which are not used in the code
List<Assembly> dataAssembliesNames =
   AppDomain.CurrentDomain.GetAssemblies()
            .Where(assembly => AssembliesToLoad.Any(a => assembly.GetName().Name == a))
            .ToList();

var loadedPaths = dataAssembliesNames.Select(a => a.Location).ToArray();

var compareConfig = StringComparison.InvariantCultureIgnoreCase;
var referencedPaths = Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll")
    .Where(f =>
    {
       // filtering the ones which are in above list
       var lastIndexOf = f.LastIndexOf("\\", compareConfig);
       var dllIndex = f.LastIndexOf(".dll", compareConfig);

       if (-1 == lastIndexOf || -1 == dllIndex)
       {
          return false;
       }

       return AssembliesToLoad.Any(aName => aName == 
          f.Substring(lastIndexOf + 1, dllIndex - lastIndexOf - 1));
     });

var toLoad = referencedPaths.Where(r => !loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase)).ToList();

toLoad.ForEach(path => dataAssembliesNames.Add(AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(path))));

if (dataAssembliesNames.Count() != AssembliesToLoad.Length)
{
   throw new Exception("Not all assemblies were loaded into the  project!");
}

0

컴파일 타임에 코드가 참조되지 않는 어셈블리가있는 경우 해당 어셈블리는 프로젝트 또는 너겟 패키지를 참조로 추가 한 경우에도 다른 어셈블리에 대한 참조로 포함되지 않습니다. 이것은 관계없이이다 Debug또는 Release설정, 이러한 경우 등 코드 최적화, 빌드, 당신은 명시 적으로 호출해야합니다 Assembly.LoadFrom(dllFileName)어셈블리를로드받을 수 있습니다.


0

참조 된 어셈블리를 이름으로 가져 오려면 다음 방법을 사용할 수 있습니다.

public static Assembly GetAssemblyByName(string name)
{
    var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == name);
    if (asm == null)
        asm = AppDomain.CurrentDomain.Load(name);
    return asm;
}

0

내 winforms 응용 프로그램에서 JavaScript (WebView2 컨트롤에서)에 다양한 .NET 항목을 호출 할 수있는 가능성을 제공합니다 Microsoft.VisualBasic.Interaction(예 : 어셈블리 Microsoft.VisualBasic.dll의 메서드 InputBox()등).

그러나 내 응용 프로그램은 해당 어셈블리를 사용하지 않으므로 어셈블리가로드되지 않습니다.

따라서 어셈블리를 강제로로드하기 위해 Form1_Load에 간단히 추가했습니다.

if (DateTime.Now < new DateTime(1000, 1, 1, 0, 0, 0)) { // never happens
  Microsoft.VisualBasic.Interaction.Beep();
  // you can add more things here
}

컴파일러는 어셈블리가 필요할 수 있다고 생각하지만 실제로는 결코 발생하지 않습니다.

매우 정교한 솔루션은 아니지만 빠르고 더럽습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.