C# 팁: ExpandoObject 활용

본 내용은 CODE4IT의 C# Tip: Convert ExpandoObjects to IDictionary의 내용을 보완한 글입니다.

ExpandoObject

숙련된 C# 개발자라도 ExpandoObject를 처음 들었거나 알기는 하지만 사용해보지 않았을 수 있습니다.

ExpandoObject 는 dynamic 형식을 통해 동적으로 속성을 추가하고 사용할 수 있게 해줍니다.

dynamic myObj = new ExpandoObject();
myObj.Name = "Davide";
myObj.Age = 30;

myObj 인스턴스는 ExpandoObject 인스턴스이지만 dynamic 형식이므로 속성의 존재 유무를 컴파일 시점에서 확인하지 않으며 속성을 사용하는 런타임 시점에서 새로운 속성명으로 값을 설정하고 읽을 수 있습니다.

마치 파이썬과 같은 동적 언어 같지요?

동적으로 추가된 속성은 어떻게 조회할 수 있나요?

ExpandoObject 인스턴스는 동적으로 속성이 계속 추가될 수 있기 때문에 어떤 속성이 추가되었는지 조회가 필요합니다.

이는 ExpandoObject가 구현한 인터페이스를 통해 가능한데요

public sealed class ExpandoObject : ICollection<KeyValuePair<string, object?>>, IEnumerable<KeyValuePair<string, object?>>, IEnumerable, IDictionary<string, object?>, INotifyPropertyChanged, IDynamicMetaObjectProvider

KeyValuePair<string,object?>ICollection 인터페이스 및 IDictionary<string,object?> 인터페이스가 구현되어 있기 때문에 다음 처럼 사용할 수 있습니다.

using System.ComponentModel;
using System.Dynamic;

dynamic p = new ExpandoObject();

// Console.WriteLine(p.Name); // 아직 Name 속성이 없으므로 예외

p.Name = "David";
p.Age = 30;

IDictionary<string, object?> dic = p;

dic["■■■"] = "Male";

Console.WriteLine(p.■■■);

foreach (var kv in p)
{
    Console.WriteLine(kv);
}

| 출력

Male
[Name, David]
[Age, 30]
[■■■, Male]

반응 개체로도 사용

ExpandoObject는 또한 INotifyPropertyChanged 인터페이스를 구현해서 다음처럼 속성이 변경될 때 반응할 수 있습니다.

using System.ComponentModel;
using System.Dynamic;

dynamic p = new ExpandoObject();

(p as INotifyPropertyChanged)!.PropertyChanged += (s, e) =>
{
    Console.WriteLine($"Changed Property : {e.PropertyName}");
};

// Console.WriteLine(p.Name); // 아직 Name 속성이 없으므로 예외

p.Name = "David";
p.Age = 30;

IDictionary<string, object?> dic = p;

dic["■■■"] = "Male";

Console.WriteLine(p.■■■);

foreach (var kv in p)
{
    Console.WriteLine(kv);
}

| 출력

Changed Property : Name
Changed Property : Age
Changed Property : ■■■
Male
[Name, David]
[Age, 30]
[■■■, Male]
5개의 좋아요

좋은 내용이지만 만약 dorFuscator와 같은 난독화 솔루션을 이용중이라면 런타임 시 오류가 발생할 가능성이 매우 높아 권장하지 않습니다.

3개의 좋아요

ExpandoObjectINotifyPropertyChanged 인터페이스를 구현했기 때문에 WPF 등에서 바인딩 개체로 사용할 수 있겠지만 아직 해보지는 않아서 간단히 구현해 보았습니다. 아래의 소스코드를 통해 확인할 수 있습니다.

| MainWindow.xaml

...
        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Label Width="80">Name</Label>
                <TextBox Width="120" Text="{Binding UserInfo.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Width="80">Age</Label>
                <TextBox Width="80" Text="{Binding UserInfo.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
                <Slider
                    Width="200"
                    Margin="8,0,0,0"
                    VerticalAlignment="Center"
                    IsSnapToTickEnabled="True"
                    Maximum="100"
                    Minimum="1"
                    TickFrequency="1"
                    Value="{Binding UserInfo.Age, Mode=TwoWay}" />
            </StackPanel>
            <Label>Output</Label>
            <StackPanel Orientation="Horizontal">
                <Label Width="80">Name</Label>
                <Label Content="{Binding UserInfo.Name, Mode=OneWay}" />
            </StackPanel>
            <StackPanel Orientation="Horizontal">
                <Label Width="80">Age</Label>
                <Label Content="{Binding UserInfo.Age, Mode=OneWay}" />
            </StackPanel>
        </StackPanel>
...

| MainWindow.xaml.cs

    public partial class MainWindow : Window
    {
        public dynamic UserInfo { get; } = new ExpandoObject();

        public MainWindow()
        {
            DataContext = this;

            InitializeComponent();

            //UserInfo.Name = "John";
        }
    }

image

소스코드

6개의 좋아요

알려주신 방식이 요런 상황에서는 꽤 유용하긴 합니다…

물론 요런 상황은 자주 없다는 게 함정…ㅋㅅㅋ


아… 써놓고 보니 이건 ExpandoObject 가 아니라 DynamicObject 군요… 죄송…;;;

5개의 좋아요

오우 멋진 경험 공유 감사드립니다…!

2개의 좋아요