ฉันจะสร้างคุณสมบัติไดนามิกใน C # ได้อย่างไร


87

ฉันกำลังมองหาวิธีสร้างคลาสด้วยชุดคุณสมบัติคงที่ ในขณะทำงานฉันต้องการเพิ่มคุณสมบัติไดนามิกอื่น ๆ ให้กับวัตถุนี้จากฐานข้อมูล ฉันต้องการเพิ่มความสามารถในการเรียงลำดับและการกรองให้กับวัตถุเหล่านี้ด้วย

ฉันจะทำสิ่งนี้ใน C # ได้อย่างไร


3
จุดประสงค์ของคลาสนี้คืออะไร? คำขอของคุณทำให้ฉันสงสัยว่าคุณต้องการรูปแบบการออกแบบหรืออะไรบางอย่างจริงๆแม้ว่าจะไม่รู้ว่ากรณีการใช้งานของคุณคืออะไรหมายความว่าฉันไม่มีข้อเสนอแนะ
Brian

คำตอบ:


60

คุณอาจใช้พจนานุกรมพูด

Dictionary<string,object> properties;

ฉันคิดว่าในกรณีส่วนใหญ่ที่ทำอะไรคล้าย ๆ กันมันก็ทำแบบนี้
ไม่ว่าในกรณีใดคุณจะไม่ได้รับอะไรเลยจากการสร้างคุณสมบัติ "จริง" ด้วย set และ get accessors เนื่องจากจะถูกสร้างขึ้นในขณะรันไทม์เท่านั้นและคุณจะไม่ได้ใช้มันในโค้ดของคุณ ...

นี่คือตัวอย่างที่แสดงการใช้งานการกรองและการเรียงลำดับที่เป็นไปได้ (ไม่มีการตรวจสอบข้อผิดพลาด):

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การใช้งาน ฯลฯ เพื่อคุณสมบัติการแสดงผล

ในการเชื่อมโยงกับรายการคุณจะต้องITypedListและIList; สำหรับการเรียงลำดับขั้นพื้นฐาน: IBindingList; สำหรับการกรองและการเรียงลำดับขั้นสูง: IBindingListView; สำหรับการสนับสนุน "แถวใหม่" แบบเต็ม ( DataGridView): ICancelAddNew(ว้าว!)

มันเป็นงานจำนวนมากแม้ว่า DataTable(แม้ว่าฉันจะเกลียดมัน) เป็นวิธีที่ถูกในการทำสิ่งเดียวกัน หากคุณไม่ต้องการการผูกข้อมูลเพียงแค่ใช้แฮชแท็ก ;-p

นี่เป็นตัวอย่างง่ายๆ - แต่คุณสามารถทำอะไรได้มากกว่านี้ ...


ขอบคุณ ... สามารถ databind ได้โดยตรงคือสิ่งที่ฉันกำลังมองหา โดยพื้นฐานแล้ววิธีที่ถูกคือการแปลคอลเล็กชันอ็อบเจ็กต์เป็น DataTable จากนั้นผูกตารางแทน ฉันเดาว่ามีหลายสิ่งที่ต้องกังวลหลังจากการแปลงเช่นกัน .. ขอบคุณสำหรับการป้อนข้อมูล
Eatdoku

Silverlight ไม่รองรับการผูกข้อมูลผ่าน ICustomTypeDescriptor :(
Curt Hagenlocher

ในฐานะที่เป็นโหนดด้านข้างไปยังบันทึกด้านข้าง Silverlight 5 ได้เปิดตัวอินเทอร์เฟซ ICustomTypeProvider แทน ICustomTypeDescriptor ICustomTypeProvider ถูกย้ายไปยัง. NET Framework 4.5 ในเวลาต่อมาเพื่อให้สามารถพกพาได้ระหว่าง Silverlight และ .NET Framework :).
Edward



12

ฉันไม่แน่ใจว่าคุณต้องการทำในสิ่งที่คุณบอกว่าคุณอยากทำจริงๆแต่มันไม่ใช่สำหรับฉันที่จะหาเหตุผลว่าทำไม!

คุณไม่สามารถเพิ่มคุณสมบัติให้กับคลาสได้หลังจากที่ถูก JITed แล้ว

สิ่งที่ใกล้เคียงที่สุดที่คุณจะได้รับคือการสร้างประเภทย่อยแบบไดนามิกด้วย Reflection ปล่อยและคัดลอกฟิลด์ที่มีอยู่ แต่คุณต้องอัปเดตการอ้างอิงทั้งหมดไปยังวัตถุด้วยตัวคุณเอง

คุณจะไม่สามารถเข้าถึงคุณสมบัติเหล่านั้นได้ในเวลาคอมไพล์

สิ่งที่ต้องการ:

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 สำหรับคุณสมบัติไดนามิก:

เมื่อเร็ว ๆ นี้ฉันมีข้อกำหนดในการผูกมุมมองกริดกับวัตถุเรกคอร์ดที่อาจมีคุณสมบัติจำนวนเท่าใดก็ได้ที่สามารถเพิ่มและลบออกได้ในขณะรันไทม์ นี่คือการอนุญาตให้ผู้ใช้เพิ่มคอลัมน์ใหม่ในชุดผลลัพธ์เพื่อป้อนชุดข้อมูลเพิ่มเติม

สิ่งนี้สามารถทำได้โดยการมี 'แถว' ข้อมูลแต่ละรายการเป็นพจนานุกรมโดยมีคีย์เป็นชื่อคุณสมบัติและค่าเป็นสตริงหรือคลาสที่สามารถเก็บค่าของคุณสมบัติสำหรับแถวที่ระบุ แน่นอนว่าการมี List of Dictionary object จะไม่สามารถผูกติดกับกริดได้ นี่คือที่ที่ ICustomTypeDescriptor เข้ามา

ด้วยการสร้างคลาส wrapper สำหรับ 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 นี่คือคลาส Property Descriptor แบบกำหนดเองที่จัดการวิธีการตั้งค่าและเรียกใช้คุณสมบัติ ดูการใช้งานด้านล่าง:

/// <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 wrapper มิฉะนั้นการร่ายจะล้มเหลว เมื่อสร้างตัวอธิบายคีย์ (ชื่อคุณสมบัติ) จะถูกส่งผ่านและใช้เพื่อค้นหาพจนานุกรมเพื่อให้ได้ค่าที่ถูกต้อง

นำมาจากบล็อกของฉันที่:

การใช้งาน ICustomTypeDescriptor สำหรับคุณสมบัติไดนามิก


ฉันรู้ว่าคุณเขียนสิ่งนี้มาตลอด แต่คุณควรใส่รหัสบางส่วนในคำตอบของคุณหรืออ้างอิงบางอย่างจากโพสต์ของคุณ ฉันคิดว่ามันอยู่ในกฎ - คำตอบของคุณแทบจะไม่มีความหมายเลยถ้าลิงค์ของคุณมืดไป จะไม่ลงคะแนนเพราะคุณสามารถค้นหา ICustomTypeDescriptor บน MSDN ( msdn.microsoft.com/en-us/library/… )
David Schwartz

@DavidSchwartz - เพิ่มแล้ว
WraithNath

ฉันมีปัญหาในการออกแบบเช่นเดียวกับคุณนี่เป็นวิธีแก้ปัญหาที่ดี ไม่ว่าสิ่งนี้หรือฉันจะทำอย่างไรกับ databinding และควบคุม ui ด้วยตนเองผ่านรหัสที่อยู่เบื้องหลังในมุมมองของฉัน คุณสามารถผูกสองทางด้วยวิธีนี้ได้หรือไม่?
ม้วน

@rolls ใช่คุณทำได้เพียงตรวจสอบให้แน่ใจว่าตัวบอกคุณสมบัติของคุณไม่ได้ส่งกลับมาว่าอ่านอย่างเดียว เมื่อเร็ว ๆ นี้ฉันได้ใช้วิธีการที่คล้ายกันสำหรับอย่างอื่นเช่นกันซึ่งแสดงข้อมูลในรายการต้นไม้ที่อนุญาตให้แก้ไขข้อมูลในเซลล์ได้
WraithNath

1

คุณควรตรวจสอบ DependencyObjects ที่ใช้โดย WPF ซึ่งเป็นไปตามรูปแบบที่คล้ายกันโดยคุณสมบัติสามารถกำหนดได้ในรันไทม์ ดังที่ได้กล่าวไว้ข้างต้นสิ่งนี้ชี้ให้เห็นถึงการใช้ตารางแฮช

สิ่งหนึ่งที่มีประโยชน์อื่น ๆ ที่จะมีลักษณะที่เป็น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เป็นและในการก่อสร้างฉันเริ่มต้นมันIDictionarynew SortedDictionary<string, string>()


ฉันกำลังลองวิธีแก้ปัญหาของคุณ ฉันกำลังตั้งค่าที่ฝั่งบริการว่าDTO ของฉันอยู่record[name_column] = DBConvert.To<string>(r[name_column]);ที่ไหน recordฉันจะรับค่านี้ที่ฝั่งไคลเอ็นต์ได้อย่างไร
RobertKing

1

ฉันไม่แน่ใจว่าเหตุผลของคุณคืออะไรและแม้ว่าคุณจะสามารถดึงมันออกมาได้ด้วย Reflection Emit (ฉันไม่แน่ใจว่าคุณทำได้) แต่ก็ไม่ได้เป็นความคิดที่ดี สิ่งที่น่าจะเป็นความคิดที่ดีกว่าคือการมีพจนานุกรมบางประเภทและคุณสามารถปิดการเข้าถึงพจนานุกรมด้วยวิธีการในชั้นเรียน ด้วยวิธีนี้คุณสามารถจัดเก็บข้อมูลจากฐานข้อมูลในพจนานุกรมนี้แล้วดึงข้อมูลโดยใช้วิธีการเหล่านั้น


0

ทำไมไม่ใช้ตัวสร้างดัชนีที่มีชื่อคุณสมบัติเป็นค่าสตริงที่ส่งไปยังตัวทำดัชนี


0

คุณไม่สามารถให้ชั้นเรียนของคุณเปิดเผยวัตถุพจนานุกรมได้หรือ แทนที่จะ "แนบคุณสมบัติเพิ่มเติมให้กับออบเจ็กต์" คุณสามารถแทรกข้อมูลของคุณ (พร้อมตัวระบุบางตัว) ลงในพจนานุกรมได้ในขณะทำงาน


0

หากเป็นการเชื่อมโยงคุณสามารถอ้างอิงตัวทำดัชนีจาก XAML

Text="{Binding [FullName]}"

นี่คือการอ้างอิงตัวทำดัชนีคลาสด้วยคีย์ "FullName"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.