Szukam sposobu na stworzenie klasy z zestawem właściwości statycznych. W czasie wykonywania chcę mieć możliwość dodawania innych właściwości dynamicznych do tego obiektu z bazy danych. Chciałbym również dodać możliwości sortowania i filtrowania do tych obiektów.
Jak to zrobić w C #?
Odpowiedzi:
Możesz na przykład użyć słownika
Dictionary<string,object> properties;
Myślę, że w większości przypadków, gdy robi się coś podobnego, robi się to w ten sposób.
W każdym razie nic nie zyskałbyś na tworzeniu "prawdziwej" właściwości z akcesoriami set i get, ponieważ byłaby ona tworzona tylko w czasie wykonywania i nie używałbyś jej w swoim kodzie ...
Oto przykład pokazujący możliwą implementację filtrowania i sortowania (bez sprawdzania błędów):
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"]); } } } }
źródło
Jeśli to potrzebne do celów wiązania danych, można to zrobić z niestandardowym modelu deskryptora ... poprzez wdrożenie
ICustomTypeDescriptor
,TypeDescriptionProvider
i / lubTypeCoverter
można tworzyć własnePropertyDescriptor
instancji w czasie wykonywania. To jest to, co podoba kontroleDataGridView
,PropertyGrid
korzystanie etc właściwościach ekranu.Aby powiązać się z listami, potrzebujesz
ITypedList
iIList
; do podstawowego sortowania:IBindingList
; dla filtrowania i zaawansowanego sortowania:IBindingListView
; dla pełnej obsługi "nowego wiersza" (DataGridView
):ICancelAddNew
(uff!).To jednak dużo pracy.
DataTable
(chociaż tego nienawidzę) to tani sposób na zrobienie tego samego. Jeśli nie potrzebujesz wiązania danych, po prostu użyj tablicy haszującej ;-pOto prosty przykład - ale możesz zrobić o wiele więcej ...
źródło
Użyj ExpandoObject jak ViewBag w MVC 3.
źródło
Utwórz tabelę z haszowaniem o nazwie „Właściwości” i dodaj do niej swoje właściwości.
źródło
Nie jestem pewien, czy naprawdę chcesz robić to, co mówisz, że chcesz , ale nie mam powodu, dlaczego!
Nie można dodawać właściwości do klasy po jej JIT.
Najbliższym możliwym rozwiązaniem byłoby dynamiczne utworzenie podtypu za pomocą funkcji Odbicie.Emituj i kopiuj istniejące pola, ale musisz samodzielnie zaktualizować wszystkie odniesienia do obiektu.
Nie byłbyś również w stanie uzyskać dostępu do tych właściwości w czasie kompilacji.
Coś jak:
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>(); }
Nie mam zainstalowanego VS na tym komputerze, więc daj mi znać, jeśli są jakieś poważne błędy (cóż ... poza ogromnymi problemami z wydajnością, ale nie napisałem specyfikacji!)
Teraz możesz go użyć:
Dynamic d = new Dynamic(); d = d.Add("MyProperty", 42); Console.WriteLine(d.GetType().GetProperty("MyProperty").GetValue(d, null));
Można go również używać jak normalnej właściwości w języku obsługującym późne wiązanie (na przykład VB.NET)
źródło
Zrobiłem dokładnie to za pomocą interfejsu ICustomTypeDescriptor i słownika.
Implementowanie ICustomTypeDescriptor dla właściwości dynamicznych:
Niedawno miałem wymóg powiązania widoku siatki z obiektem rekordu, który może mieć dowolną liczbę właściwości, które można dodawać i usuwać w czasie wykonywania. Miało to umożliwić użytkownikowi dodanie nowej kolumny do zestawu wyników w celu wprowadzenia dodatkowego zestawu danych.
Można to osiągnąć, mając każdy „wiersz” danych jako słownik, w którym klucz jest nazwą właściwości, a wartością jest ciąg lub klasa, która może przechowywać wartość właściwości dla określonego wiersza. Oczywiście posiadanie listy obiektów słownika nie będzie mogło być przypisane do siatki. W tym miejscu pojawia się ICustomTypeDescriptor.
Tworząc klasę opakowania dla Dictionary i dostosowując ją do interfejsu ICustomTypeDescriptor, można przesłonić zachowanie zwracania właściwości obiektu.
Spójrz na implementację klasy danych „wiersz” poniżej:
/// <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 //--------------------------------------------------------------------------------------------------------------------------- }
Uwaga: w metodzie GetProperties mogę buforować pliki PropertyDescriptors po przeczytaniu w celu zwiększenia wydajności, ale ponieważ dodaje i usuwa kolumny w czasie wykonywania, zawsze chcę, aby były odbudowywane
W metodzie GetProperties zauważysz również, że Deskryptory właściwości dodane do wpisów słownika są typu TestResultPropertyDescriptor. Jest to niestandardowa klasa deskryptora właściwości, która zarządza ustawieniem i pobieraniem właściwości. Spójrz na implementację poniżej:
/// <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 //--------------------------------------------------------------------------------------------------------------------------- }
Główne właściwości tej klasy to GetValue i SetValue. Tutaj możesz zobaczyć rzutowany komponent jako słownik oraz ustawioną lub pobraną wartość klucza w nim zawartego. Ważne jest, aby słownik w tej klasie był tego samego typu w klasie otoki Row, w przeciwnym razie rzutowanie zakończy się niepowodzeniem. Podczas tworzenia deskryptora klucz (nazwa właściwości) jest przekazywany i używany do wysyłania zapytań do słownika w celu uzyskania poprawnej wartości.
Zrobione z mojego bloga pod adresem:
Implementacja ICustomTypeDescriptor dla właściwości dynamicznych
źródło
Należy zajrzeć do DependencyObjects używanych przez WPF, które są zgodne z podobnym wzorcem, w którym właściwości można przypisać w czasie wykonywania. Jak wspomniano powyżej, ostatecznie wskazuje to na użycie tabeli skrótów.
Inną przydatną rzeczą, na którą warto zwrócić uwagę, jest CSLA.Net . Kod jest dostępny bezpłatnie i wykorzystuje niektóre z zasad \ wzorców, o których myślisz.
Również jeśli patrzysz na sortowanie i filtrowanie, zgaduję, że będziesz używać jakiejś siatki. Użytecznym interfejsem do zaimplementowania jest ICustomTypeDescriptor, który pozwala skutecznie zastąpić to, co dzieje się, gdy obiekt zostanie odzwierciedlony, dzięki czemu można skierować reflektor na własną wewnętrzną tabelę haszowania obiektu.
źródło
Jako zamiennik dla części kodu orsogufo, ponieważ ostatnio poszedłem ze słownikiem dla tego samego problemu, oto mój operator []:
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); } } }
W przypadku tej implementacji metoda ustawiająca doda nowe pary klucz-wartość,
[]=
jeśli nie istnieją one jeszcze w słowniku.Również dla mnie
properties
jestIDictionary
i w konstruktorach, w których go inicjalizujęnew SortedDictionary<string, string>()
.źródło
record[name_column] = DBConvert.To<string>(r[name_column]);
gdzierecord
jest moje DTO. Jak uzyskać tę wartość po stronie klienta?Nie jestem pewien, jakie są Twoje powody, a nawet jeśli mógłbyś to jakoś zrobić za pomocą Emitowania Refleksji (nie jestem pewien, czy możesz), to nie brzmi jak dobry pomysł. Prawdopodobnie lepszym pomysłem jest posiadanie jakiegoś rodzaju słownika i możesz zawijać dostęp do słownika za pomocą metod w swojej klasie. W ten sposób możesz przechowywać dane z bazy danych w tym słowniku, a następnie pobierać je przy użyciu tych metod.
źródło
Dlaczego nie użyć indeksatora z nazwą właściwości jako wartością ciągu przekazaną do indeksatora?
źródło
Czy nie moglibyście po prostu udostępnić swojej klasie obiektu Dictionary? Zamiast „dołączać więcej właściwości do obiektu”, możesz po prostu wstawić swoje dane (z jakimś identyfikatorem) do słownika w czasie wykonywania.
źródło
Jeśli jest przeznaczony do powiązania, możesz odwoływać się do indeksatorów z XAML
Text="{Binding [FullName]}"
Tutaj odwołuje się do indeksatora klas z kluczem „FullName”
źródło