[ChatGPT-4] .NET에서 같은 이름의 Assembly를 다른 이름으로 Load 하기

친구가 아래와 같이 물어봤는데, 한번도 생각해 본 적이 없는 개념이었다가 막상 될 거 같아서 질문을 해봤는데 제 예측과 비슷하게 답변을 해주네요.


Q. C#쪽 관련해서 하나 물어보고 싶은 게 있는데
혹시 1개의 어플리케이션에서 동일한 이름을 가진 복수의 .dll 파일을 상황에 따라 구분해서 메서드를 쓰고 싶은데 어찌 하는지 혹시 알고 있는거 있어 ?

A. C#에서 동일한 이름을 가진 복수의 .dll 파일을 사용하려면, 어셈블리 로드 및 바인딩 과정을 명시적으로 제어해야 합니다. 이를 위해 다음과 같은 방법을 사용할 수 있습니다.

  1. AppDomain의 AssemblyResolve 이벤트를 사용하여 어셈블리를 로드합니다.
  2. 각각의 어셈블리를 별도의 폴더에 배치하고, 애플리케이션에서 해당 폴더를 참조하도록 설정합니다.

다음은 AssemblyResolve 이벤트를 사용하는 예입니다.

using System;
using System.IO;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;

        // 어셈블리 A와 B에서 동일한 이름의 메서드를 호출합니다.
        dynamic assemblyAInstance = CreateInstance("AssemblyA.ClassName");
        assemblyAInstance.MethodName();

        dynamic assemblyBInstance = CreateInstance("AssemblyB.ClassName");
        assemblyBInstance.MethodName();
    }

    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
    {
        AssemblyName assemblyName = new AssemblyName(args.Name);
        string assemblyFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyName.Name);

        if (!Directory.Exists(assemblyFolder))
            return null;

        string assemblyPath = Path.Combine(assemblyFolder, assemblyName.Name + ".dll");
        if (!File.Exists(assemblyPath))
            return null;

        return Assembly.LoadFrom(assemblyPath);
    }

    private static object CreateInstance(string typeName)
    {
        Type type = Type.GetType(typeName, true);
        return Activator.CreateInstance(type);
    }
}

위 예제에서는 OnResolveAssembly 메서드를 통해 각각의 어셈블리가 별도의 폴더에 위치하고 있는 것을 가정하였습니다. 이렇게 하면 동일한 이름의 어셈블리를 구분하여 메서드를 호출할 수 있습니다.

4개의 좋아요

Q. AppDomain이 어떤 개념이야?

A. AppDomain은 .NET Framework에서 프로세스 내의 격리된 실행 환경을 나타냅니다. 각 AppDomain은 독립적인 메모리 공간을 가지며, 서로 다른 AppDomain 간에는 직접적인 참조나 통신이 불가능합니다. AppDomain을 사용하면, 안전하고 효율적인 방식으로 여러 어셈블리를 동시에 실행하거나, 독립적인 어셈블리를 로드하고 언로드할 수 있습니다.

AppDomain의 주요 기능은 다음과 같습니다:

  1. 격리: 서로 다른 AppDomain은 독립적인 메모리 공간을 가지므로, 한 AppDomain에서 발생한 오류나 문제가 다른 AppDomain에 영향을 미치지 않습니다.
  2. 보안: AppDomain은 각각의 어셈블리에 적용되는 권한을 제한할 수 있어, 어플리케이션의 보안을 강화할 수 있습니다.
  3. 버전 관리: 서로 다른 버전의 어셈블리를 각기 다른 AppDomain에 로드할 수 있어, 버전 충돌 문제를 해결할 수 있습니다.
  4. 어셈블리 로드 및 언로드: AppDomain은 독립적으로 어셈블리를 로드하고 언로드할 수 있어, 어플리케이션의 리소스 사용을 최적화할 수 있습니다.

AppDomain은 주로 다중 어플리케이션 호스팅, 플러그인 시스템, 샌드박스 환경에서의 코드 실행 등의 상황에서 사용됩니다. 하지만 .NET Core 및 .NET 5 이상에서는 AppDomain을 지원하지 않고, 대신 AssemblyLoadContext를 사용하여 어셈블리 로드와 언로드를 관리합니다.

1개의 좋아요

Q. 위에 준 소스코드에서 Type.GetType에서 오류가 발생해

A. 죄송합니다. 제가 놓친 부분이 있었네요. Type.GetType() 메서드는 현재 로드된 어셈블리 내에서만 타입을 찾습니다. 따라서, 외부 어셈블리에서 타입을 로드하려면 어셈블리의 전체 이름을 명시해야 합니다. 아래 수정된 CreateInstance 메서드를 사용해 주세요.

private static object CreateInstance(string assemblyName, string typeName)
{
    Assembly assembly = Assembly.Load(assemblyName);
    Type type = assembly.GetType(typeName, true);
    return Activator.CreateInstance(type);
}

Main() 메서드에서 CreateInstance를 호출할 때 어셈블리 이름을 인자로 전달해야 합니다.

// 어셈블리 A와 B에서 동일한 이름의 메서드를 호출합니다.
dynamic assemblyAInstance = CreateInstance("AssemblyA", "AssemblyA.ClassName");
assemblyAInstance.MethodName();

dynamic assemblyBInstance = CreateInstance("AssemblyB", "AssemblyB.ClassName");
assemblyBInstance.MethodName();

이제 수정된 코드로 외부 어셈블리에서 동일한 이름을 가진 복수의 .dll 파일을 정상적으로 로드할 수 있습니다.

1개의 좋아요

Q. 어셈블리의 이름이 ClassLibrary1로 같고 Class이름이 Class1 로 모두 같은 4개의 어셈블리를 로드해야해

A. 4개의 어셈블리가 모두 같은 이름을 가지고 있고 클래스 이름도 같을 경우, 각 어셈블리를 별도의 폴더에 저장하고 폴더 이름을 기준으로 구분하여 로드할 수 있습니다. 이를 위해 CreateInstance 메서드와 OnResolveAssembly 메서드를 수정해야 합니다.

using System;
using System.IO;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;

        // 4개의 어셈블리에서 동일한 이름의 메서드를 호출합니다.
        dynamic assembly1Instance = CreateInstance("Folder1", "ClassLibrary1.Class1");
        assembly1Instance.MethodName();

        dynamic assembly2Instance = CreateInstance("Folder2", "ClassLibrary1.Class1");
        assembly2Instance.MethodName();

        dynamic assembly3Instance = CreateInstance("Folder3", "ClassLibrary1.Class1");
        assembly3Instance.MethodName();

        dynamic assembly4Instance = CreateInstance("Folder4", "ClassLibrary1.Class1");
        assembly4Instance.MethodName();
    }

    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
    {
        AssemblyName assemblyName = new AssemblyName(args.Name);
        string assemblyFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyName.Name);

        if (!Directory.Exists(assemblyFolder))
            return null;

        string assemblyPath = Path.Combine(assemblyFolder, assemblyName.Name + ".dll");
        if (!File.Exists(assemblyPath))
            return null;

        return Assembly.LoadFrom(assemblyPath);
    }

    private static object CreateInstance(string folderName, string typeName)
    {
        string assemblyName = typeName.Substring(0, typeName.LastIndexOf('.'));
        string assemblyFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName);
        string assemblyPath = Path.Combine(assemblyFolder, assemblyName + ".dll");

        Assembly assembly = Assembly.LoadFrom(assemblyPath);
        Type type = assembly.GetType(typeName, true);
        return Activator.CreateInstance(type);
    }
}

이제 CreateInstance 메서드에서 첫 번째 매개변수로 각 어셈블리가 저장된 폴더 이름을 전달하면, 해당 폴더에서 동일한 이름을 가진 .dll 파일을 로드하여 메서드를 호출할 수 있습니다.

1개의 좋아요

Q. 어셈블리 이름이 동일하다고 로드가 안돼

A. 어셈블리 이름이 동일하면 Assembly.LoadFrom()Assembly.Load() 메서드가 기본적으로 어셈블리를 구분하지 못합니다. 이 문제를 해결하려면 각 어셈블리에 고유한 AssemblyName을 설정해야 합니다. 다음 단계를 수행하여 각 어셈블리에 고유한 AssemblyName을 설정하세요.

  1. 각 프로젝트의 속성을 열고, “애플리케이션” 탭에서 “어셈블리 정보” 버튼을 클릭합니다.
  2. “어셈블리 정보” 창에서 “버전” 섹션의 "어셈블리 버전"과 "파일 버전"을 변경하여 각 어셈블리에 고유한 버전을 할당합니다.
  3. 변경 사항을 저장하고 프로젝트를 다시 빌드합니다.

각 어셈블리에 고유한 AssemblyName을 설정한 후, 이전에 제공한 코드를 사용하여 동일한 이름의 여러 어셈블리를 로드하고 메서드를 호출할 수 있습니다.

만약 이 방법이 적용되지 않는 경우, 각 어셈블리의 이름을 변경하여 구분해야 합니다. 각 어셈블리의 이름을 변경한 후, 이전에 제공한 코드에서 폴더 및 어셈블리 이름을 업데이트하여 사용하세요.

1개의 좋아요

Q. 원본 어셈블리는 그대로 놔두고, AppDomain에서 로드해서 사용할 때 어셈블리 이름을 변경해서 로드하는 방법은 없어?

A. 어셈블리를 로드할 때 AppDomain에서 어셈블리 이름을 변경하여 로드하는 것은 직접적으로 지원되지 않습니다. 그러나 간접적으로 구현할 수 있는 방법이 있습니다. 원본 어셈블리를 복사한 후 복사본에 고유한 어셈블리 이름을 할당하고, 해당 복사본을 로드하는 방식입니다.

다음 코드는 어셈블리를 복사하고 수정된 이름을 할당하는 방법을 보여줍니다. 이 방법은 메모리 내에서 처리되므로 파일 시스템에 영향을 주지 않습니다.

using System;
using System.IO;
using System.Reflection;

class Program
{
    static void Main(string[] args)
    {
        AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;

        // 어셈블리 A와 B에서 동일한 이름의 메서드를 호출합니다.
        dynamic assembly1Instance = CreateInstance("Folder1", "ClassLibrary1.Class1", "ModifiedAssemblyName1");
        assembly1Instance.MethodName();

        dynamic assembly2Instance = CreateInstance("Folder2", "ClassLibrary1.Class1", "ModifiedAssemblyName2");
        assembly2Instance.MethodName();
    }

    private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
    {
        AssemblyName assemblyName = new AssemblyName(args.Name);
        string assemblyFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, assemblyName.Name);

        if (!Directory.Exists(assemblyFolder))
            return null;

        string assemblyPath = Path.Combine(assemblyFolder, assemblyName.Name + ".dll");
        if (!File.Exists(assemblyPath))
            return null;

        return Assembly.LoadFrom(assemblyPath);
    }

    private static object CreateInstance(string folderName, string typeName, string modifiedAssemblyName)
    {
        string assemblyName = typeName.Substring(0, typeName.LastIndexOf('.'));
        string assemblyFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName);
        string assemblyPath = Path.Combine(assemblyFolder, assemblyName + ".dll");

        byte[] assemblyBytes = File.ReadAllBytes(assemblyPath);
        Assembly assembly = Assembly.Load(assemblyBytes);
        AssemblyName an = assembly.GetName();
        an.Name = modifiedAssemblyName;
        Assembly modifiedAssembly = Assembly.Load(assemblyBytes);

        Type type = modifiedAssembly.GetType(typeName, true);
        return Activator.CreateInstance(type);
    }
}

위 코드는 원본 어셈블리의 내용을 읽어 Assembly.Load() 를 사용하여 메모리에 로드한 후, 로드된 어셈블리의 이름을 수정합니다. 그러나 이 방법은 어셈블리의 이름을 완전히 변경하는 것이 아니라, 로드된 어셈블리에 새 이름을 할당하여 겉으로 보이는 이름을 변경합니다. 이를 통해 동일한 이름을 가진 여러 어셈블리를 로드할 수 있습니다.

1개의 좋아요

최종 테스트에 성공한 소스링크입니다.

CSharpExample/SameNameAssemblyLoad at master · christian289/CSharpExample (github.com)

너무 뭐가 없어서 사실 올리기도 그러네요.
그냥 맨 마지막 답변인, 어셈블리 이름을 변경해서 로딩하면 됩니다.

3개의 좋아요