다음을 통해 공유


자습서: C#에서 동적 객체 생성 및 사용

동적 개체는 컴파일 시간이 아닌 런타임에 속성 및 메서드와 같은 멤버를 노출합니다. 동적 개체를 사용하면 정적 형식이나 형식과 일치하지 않는 구조체로 작업할 개체를 만들 수 있습니다. 예를 들어 동적 개체를 사용하여 유효한 HTML 태그 요소와 특성의 조합을 포함할 수 있는 HTML DOM(문서 개체 모델)을 참조할 수 있습니다. 각 HTML 문서는 고유하기 때문에 특정 HTML 문서의 멤버는 런타임에 결정됩니다. HTML 요소의 특성을 참조하는 일반적인 방법은 특성의 이름을 요소의 메서드에 GetProperty 전달하는 것입니다. HTML 요소의 id 특성을 참조하려면 먼저 요소 <div id="Div1">에 대한 참조를 <div> 가져온 다음 divElement.GetProperty("id")사용합니다. 동적 객체를 사용하는 경우 id 속성을 divElement.id로 참조할 수 있습니다.

동적 개체는 IronPython 및 IronRuby와 같은 동적 언어에 편리하게 액세스할 수 있습니다. 동적 개체를 사용하여 런타임에 해석된 동적 스크립트를 참조할 수 있습니다.

지연 바인딩을 사용하여 동적 개체를 참조합니다. 런타임에 바인딩된 개체의 형식으로 dynamic을 지정합니다. 자세한 내용은 동적을 참조하세요.

네임스페이스의 클래스를 사용하여 사용자 지정 동적 개체를 System.Dynamic 만들 수 있습니다. 예를 들어 런타임에 해당 개체의 멤버를 ExpandoObject 만들고 지정할 수 있습니다. 클래스를 상속하는 고유한 형식을 만들 수도 있습니다 DynamicObject . 그런 다음 DynamicObject 클래스의 멤버를 재정의하여 런타임 동적 기능을 제공할 수 있습니다.

이 글에는 두 가지 독립적인 절차가 포함되어 있습니다.

  • 텍스트 파일의 내용을 개체의 속성으로 동적으로 노출하는 사용자 지정 개체를 만듭니다.
  • 라이브러리를 사용하는 IronPython 프로젝트를 만듭니다.

필수 조건

  • .NET 데스크톱 개발 워크로드가 설치된 Visual Studio 2022 버전 17.3 이상 .NET 7 SDK는 이 워크로드를 선택할 때 포함됩니다.

비고

컴퓨터는 다음 지침에서 Visual Studio 사용자 인터페이스 요소 중 일부에 대해 다른 이름 또는 위치를 표시할 수 있습니다. 가지고 있는 Visual Studio 버전과 사용하는 설정에 따라 이러한 요소가 결정됩니다. 자세한 내용은 IDE 개인 설정참조하세요.

사용자 지정 동적 개체 만들기

첫 번째 연습에서는 텍스트 파일의 내용을 검색하는 사용자 지정 동적 개체를 정의합니다. 동적 속성은 검색할 텍스트를 지정합니다. 예를 들어 호출 코드가 dynamicFile.Sample지정된 경우 동적 클래스는 "Sample"으로 시작하는 파일의 모든 줄을 포함하는 문자열의 제네릭 목록을 반환합니다. 검색은 대/소문자를 구분하지 않습니다. 동적 클래스는 두 개의 선택적 인수도 지원합니다. 첫 번째 인수는 동적 클래스가 줄의 시작 부분, 줄 끝 또는 줄의 아무 곳이나 일치 항목을 검색해야 하므로 지정하는 검색 옵션 열거형 값입니다. 두 번째 인수는 동적 클래스가 검색하기 전에 각 줄에서 선행 및 후행 공백을 트리밍해야 임을 지정합니다. 예를 들어 호출 코드가 dynamicFile.Sample(StringSearchOption.Contains)지정된 경우 동적 클래스는 한 줄의 아무 곳이나 "샘플"을 검색합니다. 호출 코드가 dynamicFile.Sample(StringSearchOption.StartsWith, false)지정되면 동적 클래스는 각 줄의 시작 부분에 있는 "샘플"을 검색하고 선행 및 후행 공백을 제거하지 않습니다. 동적 클래스의 기본 동작은 각 줄의 시작 부분에 일치 항목을 검색하고 선행 및 후행 공백을 제거하는 것입니다.

사용자 지정 동적 클래스 만들기

Visual Studio를 시작합니다. 새 프로젝트 만들기를 선택합니다. 새 프로젝트 만들기 대화 상자에서 C#을 선택하고 콘솔 애플리케이션을 선택한 다음, 다음을 선택합니다. 새 프로젝트 구성 대화 상자에서 DynamicSample 입력 하고 다음을 선택합니다. 추가 정보 대화 상자에서 대상 프레임워크대해 .NET 7.0(현재)을 선택한 다음 만들기를 선택합니다. 솔루션 탐색기에서 DynamicSample 프로젝트를 마우스 오른쪽 단추로 클릭하고클래스>를 선택합니다. 이름 상자에 를 입력ReadOnlyFile한 다음 추가를 선택합니다. ReadOnlyFile.cs 또는 ReadOnlyFile.vb 파일의 맨 위에 다음 코드를 추가하여 System.IOSystem.Dynamic 네임스페이스를 가져옵니다.

using System.IO;
using System.Dynamic;

사용자 지정 동적 개체는 열거형을 사용하여 검색 조건을 결정합니다. 클래스 문 앞에 다음 열거형 정의를 추가합니다.

public enum StringSearchOption
{
    StartsWith,
    Contains,
    EndsWith
}

다음 코드 예제와 같이 클래스 문을 업데이트하여 DynamicObject 클래스를 상속합니다.

class ReadOnlyFile : DynamicObject

다음 코드를 클래스에 ReadOnlyFile 추가하여 파일 경로에 대한 프라이빗 필드와 클래스의 생성자를 정의합니다 ReadOnlyFile .

// Store the path to the file and the initial line count value.
private string p_filePath;

// Public constructor. Verify that file exists and store the path in
// the private variable.
public ReadOnlyFile(string filePath)
{
    if (!File.Exists(filePath))
    {
        throw new Exception("File path does not exist.");
    }

    p_filePath = filePath;
}
  1. 클래스에 다음 GetPropertyValue 메서드를 추가합니다 ReadOnlyFile . 이 메서드는 GetPropertyValue 입력으로 검색 조건을 사용하고 해당 검색 조건과 일치하는 텍스트 파일의 줄을 반환합니다. ReadOnlyFile 클래스에서 제공하는 동적 메서드는 GetPropertyValue 메서드를 호출하여 결과를 가져옵니다.
public List<string> GetPropertyValue(string propertyName,
                                     StringSearchOption StringSearchOption = StringSearchOption.StartsWith,
                                     bool trimSpaces = true)
{
    StreamReader sr = null;
    List<string> results = new List<string>();
    string line = "";
    string testLine = "";

    try
    {
        sr = new StreamReader(p_filePath);

        while (!sr.EndOfStream)
        {
            line = sr.ReadLine();

            // Perform a case-insensitive search by using the specified search options.
            testLine = line.ToUpper();
            if (trimSpaces) { testLine = testLine.Trim(); }

            switch (StringSearchOption)
            {
                case StringSearchOption.StartsWith:
                    if (testLine.StartsWith(propertyName.ToUpper())) { results.Add(line); }
                    break;
                case StringSearchOption.Contains:
                    if (testLine.Contains(propertyName.ToUpper())) { results.Add(line); }
                    break;
                case StringSearchOption.EndsWith:
                    if (testLine.EndsWith(propertyName.ToUpper())) { results.Add(line); }
                    break;
            }
        }
    }
    catch
    {
        // Trap any exception that occurs in reading the file and return null.
        results = null;
    }
    finally
    {
        if (sr != null) {sr.Close();}
    }

    return results;
}

GetPropertyValue 메서드 다음에 TryGetMember 클래스의 DynamicObject 메서드를 재정의하기 위해 다음 코드를 추가하십시오. TryGetMember 동적 클래스의 멤버가 요청되고 인수가 지정되지 않은 경우 메서드가 호출됩니다. 인수는 binder 참조된 멤버에 대한 정보를 포함하고 인수 result 는 지정된 멤버에 대해 반환된 결과를 참조합니다. TryGetMember 메서드는 요청된 멤버가 존재하면 부울 값 true을 반환하고, 그렇지 않으면 false을 반환합니다.

// Implement the TryGetMember method of the DynamicObject class for dynamic member calls.
public override bool TryGetMember(GetMemberBinder binder,
                                  out object result)
{
    result = GetPropertyValue(binder.Name);
    return result == null ? false : true;
}

TryGetMember 메서드 다음에 TryInvokeMember 클래스의 DynamicObject 메서드를 재정의하기 위해 다음 코드를 추가하십시오. 메서드는 TryInvokeMember 인수를 사용하여 동적 클래스의 멤버를 요청할 때 호출됩니다. 인수는 binder 참조된 멤버에 대한 정보를 포함하고 인수 result 는 지정된 멤버에 대해 반환된 결과를 참조합니다. 인수에는 args 멤버에 전달되는 인수의 배열이 포함됩니다. TryInvokeMember 메서드는 요청된 멤버가 존재하면 부울 값 true을 반환하고, 그렇지 않으면 false을 반환합니다.

메서드의 TryInvokeMember 사용자 지정 버전에서는 첫 번째 인수가 이전 단계에서 정의한 열거형의 값 StringSearchOption 이어야 합니다. 메서드는 TryInvokeMember 두 번째 인수가 부울 값이 될 것으로 예상합니다. 하나 또는 두 인수가 모두 유효한 값이면 결과를 검색하기 위해 GetPropertyValue 메서드에 전달됩니다.

// Implement the TryInvokeMember method of the DynamicObject class for
// dynamic member calls that have arguments.
public override bool TryInvokeMember(InvokeMemberBinder binder,
                                     object[] args,
                                     out object result)
{
    StringSearchOption StringSearchOption = StringSearchOption.StartsWith;
    bool trimSpaces = true;

    try
    {
        if (args.Length > 0) { StringSearchOption = (StringSearchOption)args[0]; }
    }
    catch
    {
        throw new ArgumentException("StringSearchOption argument must be a StringSearchOption enum value.");
    }

    try
    {
        if (args.Length > 1) { trimSpaces = (bool)args[1]; }
    }
    catch
    {
        throw new ArgumentException("trimSpaces argument must be a Boolean value.");
    }

    result = GetPropertyValue(binder.Name, StringSearchOption, trimSpaces);

    return result == null ? false : true;
}

파일을 저장하고 닫습니다.

샘플 텍스트 파일 만들기

솔루션 탐색기에서 DynamicSample 프로젝트를 마우스 오른쪽 단추로 클릭하고새 항목>를 선택합니다. 설치된 템플릿 창에서 일반을 선택한 다음 텍스트 파일 템플릿을 선택합니다. 이름 상자에TextFile1.txt 기본 이름을 그대로 두고 추가를 선택합니다. 다음 텍스트를 TextFile1.txt 파일에 복사합니다.

List of customers and suppliers

Supplier: Lucerne Publishing (https://www.lucernepublishing.com/)
Customer: Preston, Chris
Customer: Hines, Patrick
Customer: Cameron, Maria
Supplier: Graphic Design Institute (https://www.graphicdesigninstitute.com/)
Supplier: Fabrikam, Inc. (https://www.fabrikam.com/)
Customer: Seubert, Roxanne
Supplier: Proseware, Inc. (http://www.proseware.com/)
Customer: Adolphi, Stephan
Customer: Koch, Paul

파일을 저장하고 닫습니다.

사용자 지정 동적 개체를 사용하는 샘플 애플리케이션 만들기

솔루션 탐색기에서 Program.cs 파일을 두 번 클릭합니다. 프로시저에 Main 다음 코드를 추가하여 ReadOnlyFile 파일에 대한 클래스의 인스턴스를 만듭니다. 이 코드는 지연 바인딩을 사용하여 동적 멤버를 호출하고 "Customer" 문자열이 포함된 텍스트 줄을 검색합니다.

dynamic rFile = new ReadOnlyFile(@"..\..\..\TextFile1.txt");
foreach (string line in rFile.Customer)
{
    Console.WriteLine(line);
}
Console.WriteLine("----------------------------");
foreach (string line in rFile.Customer(StringSearchOption.Contains, true))
{
    Console.WriteLine(line);
}

파일을 저장하고 Ctrl+F5를 눌러 애플리케이션을 빌드하고 실행합니다.

동적 언어 라이브러리 호출

다음 연습에서는 동적 언어 IronPython으로 작성된 라이브러리에 액세스하는 프로젝트를 만듭니다.

사용자 지정 동적 클래스를 만들려면

Visual Studio에서 파일>새로 만들기>프로젝트를 선택합니다. 새 프로젝트 만들기 대화 상자에서 C#을 선택하고 콘솔 애플리케이션을 선택한 다음, 다음을 선택합니다. 새 프로젝트 구성 대화 상자에서 DynamicIronPythonSample 입력 하고 다음을 선택합니다. 추가 정보 대화 상자에서 대상 프레임워크대해 .NET 7.0(현재)을 선택한 다음 만들기를 선택합니다. IronPython NuGet 패키지를 설치합니다. Program.cs 파일을 편집합니다. 파일 맨 위에 다음 코드를 추가하여 IronPython 라이브러리의 Microsoft.Scripting.HostingIronPython.Hosting 네임스페이스와 System.Linq 네임스페이스를 가져옵니다.

using System.Linq;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

Main 메서드에서 다음 코드를 추가하여 IronPython 라이브러리를 호스트할 새 Microsoft.Scripting.Hosting.ScriptRuntime 개체를 만듭니다. 개체는 ScriptRuntime IronPython 라이브러리 모듈 random.py 로드합니다.

// Set the current directory to the IronPython libraries.
System.IO.Directory.SetCurrentDirectory(
   Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) +
   @"\IronPython 2.7\Lib");

// Create an instance of the random.py IronPython library.
Console.WriteLine("Loading random.py");
ScriptRuntime py = Python.CreateRuntime();
dynamic random = py.UseFile("random.py");
Console.WriteLine("random.py loaded.");

random.py 모듈을 로드하는 코드 다음에 다음 코드를 추가하여 정수 배열을 만듭니다. 배열은 배열의 값을 임의로 shuffle 정렬하는 random.py 모듈의 메서드에 전달됩니다.

// Initialize an enumerable set of integers.
int[] items = Enumerable.Range(1, 7).ToArray();

// Randomly shuffle the array of integers by using IronPython.
for (int i = 0; i < 5; i++)
{
    random.shuffle(items);
    foreach (int item in items)
    {
        Console.WriteLine(item);
    }
    Console.WriteLine("-------------------");
}

파일을 저장하고 Ctrl+F5를 눌러 애플리케이션을 빌드하고 실행합니다.

참고하십시오