Saya mencari cara untuk membuat kelas dengan satu set properti statis. Saat menjalankan, saya ingin menambahkan properti dinamis lainnya ke objek ini dari database. Saya juga ingin menambahkan kemampuan pengurutan dan pemfilteran ke objek ini.
Bagaimana saya melakukan ini di C #?
Jawaban:
Anda mungkin menggunakan kamus, katakanlah
Dictionary<string,object> properties;
Saya pikir dalam banyak kasus di mana sesuatu yang serupa dilakukan, itu dilakukan seperti ini.
Bagaimanapun, Anda tidak akan mendapatkan apa pun dari membuat properti "nyata" dengan set dan get accessors, karena ini hanya akan dibuat pada waktu proses dan Anda tidak akan menggunakannya dalam kode Anda ...
Berikut adalah contoh, yang menunjukkan kemungkinan penerapan pemfilteran dan pengurutan (tanpa pemeriksaan kesalahan):
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"]); } } } }
sumber
Jika Anda memerlukan ini untuk tujuan pengikatan data, Anda dapat melakukannya dengan model deskriptor khusus ... dengan mengimplementasikan
ICustomTypeDescriptor
,TypeDescriptionProvider
dan / atauTypeCoverter
, Anda dapat membuatPropertyDescriptor
instance Anda sendiri pada waktu proses. Seperti inilah kontrolDataGridView
,PropertyGrid
dll. Gunakan untuk menampilkan properti.Untuk mengikat ke daftar, Anda memerlukan
ITypedList
danIList
; untuk menyortir dasar:IBindingList
; untuk penyaringan dan pemilahan canggih:IBindingListView
; untuk dukungan penuh "baris baru" (DataGridView
):ICancelAddNew
(Fiuh!).Ini banyak pekerjaan.
DataTable
(meskipun saya benci) adalah cara murah untuk melakukan hal yang sama. Jika Anda tidak membutuhkan data-binding, cukup gunakan hashtable ;-pBerikut adalah contoh sederhana - tetapi Anda dapat melakukan lebih banyak lagi ...
sumber
Gunakan ExpandoObject seperti ViewBag di MVC 3.
sumber
Buat Hashtable yang disebut "Properties" dan tambahkan properti Anda ke dalamnya.
sumber
Saya tidak yakin Anda benar-benar ingin melakukan apa yang Anda katakan ingin Anda lakukan , tetapi bukan saya yang menjelaskan alasannya!
Anda tidak dapat menambahkan properti ke kelas setelah kelas tersebut di JIT.
Yang paling dekat yang bisa Anda dapatkan adalah membuat subtipe secara dinamis dengan Reflection. Hapus dan salin bidang yang ada, tetapi Anda harus memperbarui semua referensi ke objek itu sendiri.
Anda juga tidak akan dapat mengakses properti tersebut pada waktu kompilasi.
Sesuatu seperti:
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>(); }
Saya tidak memasang VS di mesin ini jadi beri tahu saya jika ada bug besar (yah ... selain masalah kinerja besar, tetapi saya tidak menulis spesifikasinya!)
Sekarang Anda dapat menggunakannya:
Dynamic d = new Dynamic(); d = d.Add("MyProperty", 42); Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));
Anda juga dapat menggunakannya seperti properti normal dalam bahasa yang mendukung pengikatan terlambat (misalnya, VB.NET)
sumber
Saya telah melakukan persis seperti ini dengan antarmuka ICustomTypeDescriptor dan Kamus.
Menerapkan ICustomTypeDescriptor untuk properti dinamis:
Saya baru-baru ini memiliki persyaratan untuk mengikat tampilan kisi ke objek rekaman yang dapat memiliki sejumlah properti yang dapat ditambahkan dan dihapus saat runtime. Ini untuk memungkinkan pengguna menambahkan kolom baru ke kumpulan hasil untuk memasukkan kumpulan data tambahan.
Hal ini dapat dicapai dengan memiliki setiap 'baris' data sebagai kamus dengan kunci menjadi nama properti dan nilainya menjadi string atau kelas yang dapat menyimpan nilai properti untuk baris yang ditentukan. Tentu saja memiliki objek List of Dictionary tidak akan bisa terikat ke kisi. Di sinilah ICustomTypeDescriptor masuk.
Dengan membuat kelas pembungkus untuk Kamus dan membuatnya sesuai dengan antarmuka ICustomTypeDescriptor, perilaku mengembalikan properti untuk suatu objek dapat diganti.
Lihatlah implementasi kelas 'baris' data di bawah ini:
/// <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 //--------------------------------------------------------------------------------------------------------------------------- }
Catatan: Dalam metode GetProperties I Could Cache the PropertyDescriptors sekali membaca untuk kinerja tetapi karena saya menambahkan dan menghapus kolom saat runtime, saya selalu ingin mereka dibangun kembali
Anda juga akan melihat dalam metode GetProperties bahwa Deskriptor Properti yang ditambahkan untuk entri kamus memiliki tipe TestResultPropertyDescriptor. Ini adalah kelas Descriptor Properti khusus yang mengatur bagaimana properti disetel dan diambil. Lihat implementasinya di bawah ini:
/// <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 //--------------------------------------------------------------------------------------------------------------------------- }
Properti utama untuk melihat kelas ini adalah GetValue dan SetValue. Di sini Anda dapat melihat komponen yang dicor sebagai kamus dan nilai kunci di dalamnya sedang Set atau diambil. Sangat penting bahwa kamus di kelas ini memiliki tipe yang sama di kelas pembungkus baris jika tidak cast akan gagal. Ketika deskriptor dibuat, kunci (nama properti) diteruskan dan digunakan untuk meminta kamus untuk mendapatkan nilai yang benar.
Diambil dari blog saya di:
Implementasi ICustomTypeDescriptor untuk properti dinamis
sumber
Anda harus melihat DependencyObjects seperti yang digunakan oleh WPF, ini mengikuti pola yang sama di mana properti dapat ditetapkan saat runtime. Seperti disebutkan di atas, ini pada akhirnya mengarah ke penggunaan tabel hash.
Satu hal lain yang berguna untuk dilihat adalah CSLA.Net . Kode ini tersedia secara gratis dan menggunakan beberapa prinsip \ pola yang muncul setelah Anda.
Juga jika Anda melihat pengurutan dan pemfilteran, saya rasa Anda akan menggunakan semacam kisi. Antarmuka yang berguna untuk diterapkan adalah ICustomTypeDescriptor, ini memungkinkan Anda secara efektif menimpa apa yang terjadi ketika objek Anda direfleksikan sehingga Anda dapat mengarahkan reflektor ke tabel hash internal objek Anda sendiri.
sumber
Sebagai pengganti beberapa kode orsogufo, karena saya sendiri baru-baru ini menggunakan kamus untuk masalah yang sama ini, berikut operator [] saya:
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); } } }
Dengan implementasi ini, penyetel akan menambahkan pasangan nilai kunci baru saat Anda menggunakan
[]=
jika pasangan tersebut belum ada dalam kamus.Juga untuk saya
properties
adalahIDictionary
dan dalam konstruktor saya menginisialisasinew SortedDictionary<string, string>()
.sumber
record[name_column] = DBConvert.To<string>(r[name_column]);
manarecord
DTO saya. Bagaimana cara mendapatkan nilai ini di sisi klien?Saya tidak yakin apa alasan Anda, dan bahkan jika Anda bisa melakukannya dengan Reflection Emit (saya 'tidak yakin Anda bisa), kedengarannya bukan ide yang bagus. Mungkin ide yang lebih baik adalah memiliki semacam Dictionary dan Anda dapat menggabungkan akses ke kamus melalui metode di kelas Anda. Dengan cara itu Anda bisa menyimpan data dari database dalam kamus ini, lalu mengambilnya menggunakan metode tersebut.
sumber
Mengapa tidak menggunakan pengindeks dengan nama properti sebagai nilai string yang diteruskan ke pengindeks?
sumber
Tidak bisakah Anda meminta kelas Anda mengekspos objek Dictionary? Alih-alih "melampirkan lebih banyak properti ke objek", Anda cukup memasukkan data Anda (dengan beberapa pengenal) ke dalam kamus saat menjalankan.
sumber
Jika itu untuk mengikat, maka Anda dapat mereferensikan pengindeks dari XAML
Text="{Binding [FullName]}"
Ini dia mereferensikan pengindeks kelas dengan kunci "FullName"
sumber