C #에서 동적 속성을 어떻게 생성합니까?


87

정적 속성 집합을 사용하여 클래스를 만드는 방법을 찾고 있습니다. 런타임에 데이터베이스에서이 개체에 다른 동적 속성을 추가 할 수 있기를 원합니다. 또한 이러한 개체에 정렬 및 필터링 기능을 추가하고 싶습니다.

C #에서 어떻게합니까?


3
이 수업의 목적은 무엇입니까? 귀하의 요청은 귀하의 사용 사례가 무엇인지 알지 못한다는 것은 실제로 제안이 없다는 것을 의미하지만 디자인 패턴이나 무언가가 정말로 필요하다는 것을 의심하게 만듭니다.
Brian

답변:


59

사전을 사용할 수 있습니다.

Dictionary<string,object> properties;

비슷한 일을하는 대부분의 경우 이렇게했다고 생각합니다.
어쨌든 set 및 get 접근자를 사용하여 "실제"속성을 생성해도 아무것도 얻지 못할 것입니다. 이는 런타임에만 생성되고 코드에서 사용하지 않기 때문입니다.

다음은 필터링 및 정렬의 가능한 구현을 보여주는 예입니다 (오류 검사 없음).

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication1 {

    class ObjectWithProperties {
        Dictionary<string, object> properties = new Dictionary<string,object>();

        public object this[string name] {
            get { 
                if (properties.ContainsKey(name)){
                    return properties[name];
                }
                return null;
            }
            set {
                properties[name] = value;
            }
        }

    }

    class Comparer<T> : IComparer<ObjectWithProperties> where T : IComparable {

        string m_attributeName;

        public Comparer(string attributeName){
            m_attributeName = attributeName;
        }

        public int Compare(ObjectWithProperties x, ObjectWithProperties y) {
            return ((T)x[m_attributeName]).CompareTo((T)y[m_attributeName]);
        }

    }

    class Program {

        static void Main(string[] args) {

            // create some objects and fill a list
            var obj1 = new ObjectWithProperties();
            obj1["test"] = 100;
            var obj2 = new ObjectWithProperties();
            obj2["test"] = 200;
            var obj3 = new ObjectWithProperties();
            obj3["test"] = 150;
            var objects = new List<ObjectWithProperties>(new ObjectWithProperties[]{ obj1, obj2, obj3 });

            // filtering:
            Console.WriteLine("Filtering:");
            var filtered = from obj in objects
                         where (int)obj["test"] >= 150
                         select obj;
            foreach (var obj in filtered){
                Console.WriteLine(obj["test"]);
            }

            // sorting:
            Console.WriteLine("Sorting:");
            Comparer<int> c = new Comparer<int>("test");
            objects.Sort(c);
            foreach (var obj in objects) {
                Console.WriteLine(obj["test"]);
            }
        }

    }
}

30

데이터 바인딩 목적으로이 작업이 필요한 경우 사용자 지정 설명자 모델을 사용하여이 작업을 수행 할 수 있습니다 ICustomTypeDescriptor. TypeDescriptionProvider및 / 또는 을 구현 하여 런타임에 TypeCoverter고유 한 PropertyDescriptor인스턴스를 만들 수 있습니다 . 이 컨트롤이 좋아하는 무엇인가 DataGridView, PropertyGrid디스플레이 속성 등을 사용.

목록에 바인딩하려면 ITypedListIList; 기본 정렬 : IBindingList; 필터링 및 고급 정렬 : IBindingListView; 완전한 "새 행"지원 ( DataGridView) : ICancelAddNew(phew!).

그것은이다 많은 작업하지만 중. DataTable(내가 싫어하지만) 같은 일을하는 저렴한 방법이다. 데이터 바인딩이 필요하지 않으면 해시 테이블을 사용하십시오 ;-p

다음은 간단한 예입니다 .하지만 더 많은 작업을 수행 할 수 있습니다.


감사합니다 ... 직접 데이터 바인딩을 할 수 있다는 것이 내가 찾던 것입니다. 따라서 기본적으로 저렴한 방법은 개체 컬렉션을 DataTable로 변환 한 다음 테이블을 대신 바인딩하는 것입니다. 변환 후에도 걱정할 게 더 많은 것 같아요 .. 입력 해 주셔서 감사합니다.
Eatdoku

참고로 ICustomTypeDescriptor를 통한 데이터 바인딩은 Silverlight에서 지원되지 않습니다. :(.
Curt Hagenlocher

사이드 노트의 사이드 노드로 Silverlight 5는 ICustomTypeDescriptor 대신 ICustomTypeProvider 인터페이스를 도입했습니다. ICustomTypeProvider는 이후 Silverlight와 .NET Framework 간의 이식성을 허용하기 위해 .NET Framework 4.5로 이식되었습니다. :).
Edward


12

"Properties"라는 Hashtable을 만들고 여기에 속성을 추가합니다.


12

당신이하고 싶다고 말한 것을 정말로하고 싶은지 잘 모르겠지만 , 그 이유를 제가 추론 할 수는 없습니다!

JIT 된 후에는 클래스에 속성을 추가 할 수 없습니다.

가장 가까운 방법은 Reflection.Emit을 사용하여 하위 유형을 동적으로 만들고 기존 필드를 복사하는 것입니다.하지만 개체에 대한 모든 참조를 직접 업데이트해야합니다.

또한 컴파일 타임에 이러한 속성에 액세스 할 수 없습니다.

다음과 같은 것 :

public class Dynamic
{
    public Dynamic Add<T>(string key, T value)
    {
        AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("DynamicAssembly"), AssemblyBuilderAccess.Run);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("Dynamic.dll");
        TypeBuilder typeBuilder = moduleBuilder.DefineType(Guid.NewGuid().ToString());
        typeBuilder.SetParent(this.GetType());
        PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(key, PropertyAttributes.None, typeof(T), Type.EmptyTypes);

        MethodBuilder getMethodBuilder = typeBuilder.DefineMethod("get_" + key, MethodAttributes.Public, CallingConventions.HasThis, typeof(T), Type.EmptyTypes);
        ILGenerator getter = getMethodBuilder.GetILGenerator();
        getter.Emit(OpCodes.Ldarg_0);
        getter.Emit(OpCodes.Ldstr, key);
        getter.Emit(OpCodes.Callvirt, typeof(Dynamic).GetMethod("Get", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(T)));
        getter.Emit(OpCodes.Ret);
        propertyBuilder.SetGetMethod(getMethodBuilder);

        Type type = typeBuilder.CreateType();

        Dynamic child = (Dynamic)Activator.CreateInstance(type);
        child.dictionary = this.dictionary;
        dictionary.Add(key, value);
        return child;
    }

    protected T Get<T>(string key)
    {
        return (T)dictionary[key];
    }

    private Dictionary<string, object> dictionary = new Dictionary<string,object>();
}

이 컴퓨터에는 VS가 설치되어 있지 않으므로 대규모 버그가 있는지 알려주십시오 (음 ... 대량 성능 문제 외에는 사양을 작성하지 않았습니다!).

이제 사용할 수 있습니다.

Dynamic d = new Dynamic();
d = d.Add("MyProperty", 42);
Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));

후기 바인딩을 지원하는 언어 (예 : VB.NET)에서 일반 속성처럼 사용할 수도 있습니다.


4

ICustomTypeDescriptor 인터페이스와 사전을 사용하여 정확히이 작업을 수행했습니다.

동적 속성에 대한 ICustomTypeDescriptor 구현 :

최근에 런타임에 추가 및 제거 할 수있는 속성을 얼마든지 가질 수있는 레코드 개체에 격자보기를 바인딩해야한다는 요구 사항이있었습니다. 이는 사용자가 추가 데이터 세트를 입력하기 위해 결과 세트에 새 열을 추가 할 수 있도록하기위한 것입니다.

이는 키가 속성 이름이고 값이 문자열 또는 지정된 행에 대한 속성 값을 저장할 수있는 클래스 인 사전으로 각 데이터 '행'을 사용하여 달성 할 수 있습니다. 물론 사전 객체 목록을 갖는 것은 그리드에 바인딩 될 수 없습니다. 이것은 ICustomTypeDescriptor가 들어오는 곳입니다.

Dictionary에 대한 래퍼 클래스를 만들고 ICustomTypeDescriptor 인터페이스를 준수하도록함으로써 개체의 속성을 반환하는 동작을 재정의 할 수 있습니다.

아래의 데이터 'row'클래스 구현을 살펴보십시오.

/// <summary>
/// Class to manage test result row data functions
/// </summary>
public class TestResultRowWrapper : Dictionary<string, TestResultValue>, ICustomTypeDescriptor
{
    //- METHODS -----------------------------------------------------------------------------------------------------------------

    #region Methods

    /// <summary>
    /// Gets the Attributes for the object
    /// </summary>
    AttributeCollection ICustomTypeDescriptor.GetAttributes()
    {
        return new AttributeCollection(null);
    }

    /// <summary>
    /// Gets the Class name
    /// </summary>
    string ICustomTypeDescriptor.GetClassName()
    {
        return null;
    }

    /// <summary>
    /// Gets the component Name
    /// </summary>
    string ICustomTypeDescriptor.GetComponentName()
    {
        return null;
    }

    /// <summary>
    /// Gets the Type Converter
    /// </summary>
    TypeConverter ICustomTypeDescriptor.GetConverter()
    {
        return null;
    }

    /// <summary>
    /// Gets the Default Event
    /// </summary>
    /// <returns></returns>
    EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
    {
        return null;
    }

    /// <summary>
    /// Gets the Default Property
    /// </summary>
    PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
    {
        return null;
    }

    /// <summary>
    /// Gets the Editor
    /// </summary>
    object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
    {
        return null;
    }

    /// <summary>
    /// Gets the Events
    /// </summary>
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
    {
        return new EventDescriptorCollection(null);
    }

    /// <summary>
    /// Gets the events
    /// </summary>
    EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
    {
        return new EventDescriptorCollection(null);
    }

    /// <summary>
    /// Gets the properties
    /// </summary>
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
    {
        List<propertydescriptor> properties = new List<propertydescriptor>();

        //Add property descriptors for each entry in the dictionary
        foreach (string key in this.Keys)
        {
            properties.Add(new TestResultPropertyDescriptor(key));
        }

        //Get properties also belonging to this class also
        PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.GetType(), attributes);

        foreach (PropertyDescriptor oPropertyDescriptor in pdc)
        {
            properties.Add(oPropertyDescriptor);
        }

        return new PropertyDescriptorCollection(properties.ToArray());
    }

    /// <summary>
    /// gets the Properties
    /// </summary>
    PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
    {
        return ((ICustomTypeDescriptor)this).GetProperties(null);
    }

    /// <summary>
    /// Gets the property owner
    /// </summary>
    object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
    {
        return this;
    }

    #endregion Methods

    //---------------------------------------------------------------------------------------------------------------------------
}

참고 : GetProperties 메서드에서 성능을 위해 읽은 PropertyDescriptors를 캐시 할 수 있었지만 런타임에 열을 추가하고 제거 할 때 항상 다시 작성하고 싶습니다.

또한 GetProperties 메서드에서 사전 항목에 추가 된 속성 설명자가 TestResultPropertyDescriptor 유형임을 알 수 있습니다. 이것은 속성을 설정하고 검색하는 방법을 관리하는 사용자 지정 속성 설명자 클래스입니다. 아래 구현을 살펴보십시오.

/// <summary>
/// Property Descriptor for Test Result Row Wrapper
/// </summary>
public class TestResultPropertyDescriptor : PropertyDescriptor
{
    //- PROPERTIES --------------------------------------------------------------------------------------------------------------

    #region Properties

    /// <summary>
    /// Component Type
    /// </summary>
    public override Type ComponentType
    {
        get { return typeof(Dictionary<string, TestResultValue>); }
    }

    /// <summary>
    /// Gets whether its read only
    /// </summary>
    public override bool IsReadOnly
    {
        get { return false; }
    }

    /// <summary>
    /// Gets the Property Type
    /// </summary>
    public override Type PropertyType
    {
        get { return typeof(string); }
    }

    #endregion Properties

    //- CONSTRUCTOR -------------------------------------------------------------------------------------------------------------

    #region Constructor

    /// <summary>
    /// Constructor
    /// </summary>
    public TestResultPropertyDescriptor(string key)
        : base(key, null)
    {

    }

    #endregion Constructor

    //- METHODS -----------------------------------------------------------------------------------------------------------------

    #region Methods

    /// <summary>
    /// Can Reset Value
    /// </summary>
    public override bool CanResetValue(object component)
    {
        return true;
    }

    /// <summary>
    /// Gets the Value
    /// </summary>
    public override object GetValue(object component)
    {
          return ((Dictionary<string, TestResultValue>)component)[base.Name].Value;
    }

    /// <summary>
    /// Resets the Value
    /// </summary>
    public override void ResetValue(object component)
    {
        ((Dictionary<string, TestResultValue>)component)[base.Name].Value = string.Empty;
    }

    /// <summary>
    /// Sets the value
    /// </summary>
    public override void SetValue(object component, object value)
    {
        ((Dictionary<string, TestResultValue>)component)[base.Name].Value = value.ToString();
    }

    /// <summary>
    /// Gets whether the value should be serialized
    /// </summary>
    public override bool ShouldSerializeValue(object component)
    {
        return false;
    }

    #endregion Methods

    //---------------------------------------------------------------------------------------------------------------------------
}

이 클래스에서 살펴볼 주요 속성은 GetValue 및 SetValue입니다. 여기에서 딕셔너리로 ​​캐스팅되는 구성 요소와 그 안의 키 값이 설정 또는 검색되는 것을 볼 수 있습니다. 이 클래스의 딕셔너리는 Row 래퍼 클래스에서 동일한 유형이어야합니다. 그렇지 않으면 캐스트가 실패합니다. 디스크립터가 생성되면 키 (속성 이름)가 전달되고 올바른 값을 얻기 위해 사전을 쿼리하는 데 사용됩니다.

내 블로그에서 가져온 :

동적 속성에 대한 ICustomTypeDescriptor 구현


나는 당신이 영원히 전에 이것을 썼다는 것을 알고 있지만, 당신은 정말로 당신의 대답에 당신의 코드의 일부를 넣거나 당신의 게시물에서 무언가를 인용해야합니다. 나는 그것이 규칙에 있다고 생각합니다. 링크가 어두워지면 대답이 거의 의미가 없습니다. 당신은 MSDN (에은 ICustomTypeDescriptor를 조회 할 수 있기 때문에 비록 downvote하지 않을 msdn.microsoft.com/en-us/library/... )
데이비드 슈워츠

@DavidSchwartz-추가됨.
WraithNath 2016

나는 당신과 똑같은 디자인 문제를 가지고 있습니다. 이것은 좋은 해결책처럼 보입니다. 글쎄, 이것 또는 나는 데이터 바인딩을 없애고 내 관점의 코드를 통해 수동으로 UI를 제어합니다. 이 접근 방식으로 양방향 바인딩을 할 수 있습니까?
롤백

@rolls 예, 가능합니다. 속성 설명자가 읽기 전용을 반환하지 않는지 확인하십시오. 나는 최근에 데이터를 셀에서 편집 할 수있는 트리 목록의 데이터를 보여주는 다른 것에 대해서도 유사한 접근 방식을 사용했습니다
WraithNath

1

WPF에서 사용하는 DependencyObject를 살펴보면 런타임에 속성을 할당 할 수있는 유사한 패턴을 따릅니다. 위에서 언급했듯이 이것은 궁극적으로 해시 테이블 사용을 가리 킵니다.

살펴볼 또 다른 유용한 것은 CSLA.Net 입니다. 이 코드는 무료로 제공되며 사용자가 추구하는 원칙 / 패턴 중 일부를 사용합니다.

또한 정렬 및 필터링을 살펴보면 일종의 그리드를 사용할 것입니다. 구현할 유용한 인터페이스는 ICustomTypeDescriptor입니다.이를 통해 개체가 반사 될 때 발생하는 일을 효과적으로 재정의 할 수 있으므로 반사기를 개체의 자체 내부 해시 테이블을 가리킬 수 있습니다.


1

orsogufo의 일부 코드를 대체하기 위해 최근에 동일한 문제에 대한 사전을 직접 사용했기 때문에 여기에 [] 연산자가 있습니다.

public string this[string key]
{
    get { return properties.ContainsKey(key) ? properties[key] : null; }

    set
    {
        if (properties.ContainsKey(key))
        {
            properties[key] = value;
        }
        else
        {
            properties.Add(key, value);
        }
    }
}

이 구현에서 setter는 []=사전에 아직없는 경우 사용할 때 새 키-값 쌍을 추가 합니다.

또한 나를 properties위해 IDictionary이고 생성자에서 new SortedDictionary<string, string>().


나는 당신의 해결책을 시도하고 있습니다. 내 DTO가 record[name_column] = DBConvert.To<string>(r[name_column]);어디에 있는지 서비스 측에서 값을 설정하고 있습니다 record. 클라이언트 측에서이 값을 어떻게 얻습니까?
Rohaan

1

나는 당신의 이유가 무엇인지 확신하지 못하며, Reflection Emit으로 어떻게 든 그것을 뽑아 낼 수 있다고해도 (당신이 할 수 있을지 모르겠습니다) 좋은 생각처럼 들리지 않습니다. 더 나은 아이디어는 일종의 사전을 갖는 것이며 클래스의 메소드를 통해 사전에 대한 액세스를 래핑 할 수 있습니다. 이렇게하면 데이터베이스의 데이터를이 사전에 저장 한 다음 해당 메서드를 사용하여 검색 할 수 있습니다.


0

속성 이름이있는 인덱서를 인덱서에 전달 된 문자열 값으로 사용하지 않는 이유는 무엇입니까?


0

클래스가 Dictionary 객체를 노출하도록 할 수는 없습니까? "객체에 더 많은 속성을 연결"하는 대신 런타임에 데이터 (일부 식별자 포함)를 사전에 삽입 할 수 있습니다.


0

바인딩 용인 경우 XAML에서 인덱서를 참조 할 수 있습니다.

Text="{Binding [FullName]}"

여기에서는 "FullName"키를 사용하여 클래스 인덱서를 참조합니다.

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