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


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.