ฉันกำลังมองหาวิธีสร้างคลาสด้วยชุดคุณสมบัติคงที่ ในขณะทำงานฉันต้องการเพิ่มคุณสมบัติไดนามิกอื่น ๆ ให้กับวัตถุนี้จากฐานข้อมูล ฉันต้องการเพิ่มความสามารถในการเรียงลำดับและการกรองให้กับวัตถุเหล่านี้ด้วย
ฉันจะทำสิ่งนี้ใน C # ได้อย่างไร
ฉันกำลังมองหาวิธีสร้างคลาสด้วยชุดคุณสมบัติคงที่ ในขณะทำงานฉันต้องการเพิ่มคุณสมบัติไดนามิกอื่น ๆ ให้กับวัตถุนี้จากฐานข้อมูล ฉันต้องการเพิ่มความสามารถในการเรียงลำดับและการกรองให้กับวัตถุเหล่านี้ด้วย
ฉันจะทำสิ่งนี้ใน C # ได้อย่างไร
คำตอบ:
คุณอาจใช้พจนานุกรมพูด
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"]);
}
}
}
}
หากคุณต้องการนี้เพื่อวัตถุประสงค์การผูกข้อมูลคุณสามารถทำเช่นนี้กับรูปแบบการให้คำอธิบายที่กำหนดเอง ... โดยการใช้ICustomTypeDescriptor, TypeDescriptionProviderและ / หรือTypeCoverterคุณสามารถสร้างของคุณเองPropertyDescriptorกรณีที่รันไทม์ นี่คือสิ่งที่ควบคุมชอบDataGridView, PropertyGridการใช้งาน ฯลฯ เพื่อคุณสมบัติการแสดงผล
ในการเชื่อมโยงกับรายการคุณจะต้องITypedListและIList; สำหรับการเรียงลำดับขั้นพื้นฐาน: IBindingList; สำหรับการกรองและการเรียงลำดับขั้นสูง: IBindingListView; สำหรับการสนับสนุน "แถวใหม่" แบบเต็ม ( DataGridView): ICancelAddNew(ว้าว!)
มันเป็นงานจำนวนมากแม้ว่า DataTable(แม้ว่าฉันจะเกลียดมัน) เป็นวิธีที่ถูกในการทำสิ่งเดียวกัน หากคุณไม่ต้องการการผูกข้อมูลเพียงแค่ใช้แฮชแท็ก ;-p
นี่เป็นตัวอย่างง่ายๆ - แต่คุณสามารถทำอะไรได้มากกว่านี้ ...
ใช้ExpandoObjectเช่น ViewBag ใน MVC 3
สร้าง Hashtable ชื่อ "Properties" และเพิ่มคุณสมบัติของคุณเข้าไป
ฉันไม่แน่ใจว่าคุณต้องการทำในสิ่งที่คุณบอกว่าคุณอยากทำจริงๆแต่มันไม่ใช่สำหรับฉันที่จะหาเหตุผลว่าทำไม!
คุณไม่สามารถเพิ่มคุณสมบัติให้กับคลาสได้หลังจากที่ถูก 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)
ฉันได้ทำสิ่งนี้ด้วยอินเทอร์เฟซ 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 มิฉะนั้นการร่ายจะล้มเหลว เมื่อสร้างตัวอธิบายคีย์ (ชื่อคุณสมบัติ) จะถูกส่งผ่านและใช้เพื่อค้นหาพจนานุกรมเพื่อให้ได้ค่าที่ถูกต้อง
นำมาจากบล็อกของฉันที่:
คุณควรตรวจสอบ DependencyObjects ที่ใช้โดย WPF ซึ่งเป็นไปตามรูปแบบที่คล้ายกันโดยคุณสมบัติสามารถกำหนดได้ในรันไทม์ ดังที่ได้กล่าวไว้ข้างต้นสิ่งนี้ชี้ให้เห็นถึงการใช้ตารางแฮช
สิ่งหนึ่งที่มีประโยชน์อื่น ๆ ที่จะมีลักษณะที่เป็นCSLA.Net โค้ดสามารถใช้ได้อย่างอิสระและใช้หลักการ \ รูปแบบที่ปรากฏตามที่คุณต้องการ
นอกจากนี้หากคุณกำลังดูการเรียงลำดับและการกรองฉันเดาว่าคุณกำลังใช้กริดบางประเภท อินเทอร์เฟซที่มีประโยชน์ในการนำไปใช้คือ ICustomTypeDescriptor ซึ่งจะช่วยให้คุณสามารถแทนที่สิ่งที่เกิดขึ้นได้อย่างมีประสิทธิภาพเมื่อวัตถุของคุณสะท้อนออกมาเพื่อให้คุณสามารถชี้ตัวสะท้อนไปยังตารางแฮชภายในของวัตถุของคุณได้
เพื่อทดแทนรหัสบางส่วนของ 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>()
record[name_column] = DBConvert.To<string>(r[name_column]);ที่ไหน recordฉันจะรับค่านี้ที่ฝั่งไคลเอ็นต์ได้อย่างไร
ฉันไม่แน่ใจว่าเหตุผลของคุณคืออะไรและแม้ว่าคุณจะสามารถดึงมันออกมาได้ด้วย Reflection Emit (ฉันไม่แน่ใจว่าคุณทำได้) แต่ก็ไม่ได้เป็นความคิดที่ดี สิ่งที่น่าจะเป็นความคิดที่ดีกว่าคือการมีพจนานุกรมบางประเภทและคุณสามารถปิดการเข้าถึงพจนานุกรมด้วยวิธีการในชั้นเรียน ด้วยวิธีนี้คุณสามารถจัดเก็บข้อมูลจากฐานข้อมูลในพจนานุกรมนี้แล้วดึงข้อมูลโดยใช้วิธีการเหล่านั้น
ทำไมไม่ใช้ตัวสร้างดัชนีที่มีชื่อคุณสมบัติเป็นค่าสตริงที่ส่งไปยังตัวทำดัชนี
คุณไม่สามารถให้ชั้นเรียนของคุณเปิดเผยวัตถุพจนานุกรมได้หรือ แทนที่จะ "แนบคุณสมบัติเพิ่มเติมให้กับออบเจ็กต์" คุณสามารถแทรกข้อมูลของคุณ (พร้อมตัวระบุบางตัว) ลงในพจนานุกรมได้ในขณะทำงาน
หากเป็นการเชื่อมโยงคุณสามารถอ้างอิงตัวทำดัชนีจาก XAML
Text="{Binding [FullName]}"
นี่คือการอ้างอิงตัวทำดัชนีคลาสด้วยคีย์ "FullName"