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

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

C #에서 어떻게합니까?



답변

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

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"]);
            }
        }

    }
}


답변

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

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

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

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


답변

MVC 3의 ViewBag와 같은 ExpandoObject를 사용하십시오 .


답변

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


답변

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

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)에서 일반 속성처럼 사용할 수도 있습니다.


답변

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 구현


답변

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

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

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