我正在寻找一种创建具有一组静态属性的类的方法。在运行时,我希望能够从数据库中向该对象添加其他动态属性。我还想为这些对象添加排序和过滤功能。
如何在C#中执行此操作?
Answers:
您可能会使用字典,例如
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等使用,以显示属性。
要绑定到列表,您需要ITypedList和IList;为基本排序:IBindingList; 用于过滤和高级排序:IBindingListView; 以获得完整的“新行”支持(DataGridView):(ICancelAddNewphe!)。
虽然这是很多工作。DataTable(尽管我讨厌)做同一件事的便宜方法。如果不需要数据绑定,只需使用哈希表;-p
这是一个简单的示例-但您可以做更多的事情...
像MVC 3中的ViewBag一样使用ExpandoObject。
我不确定您是否确实想做您想做的事情,但这不是我要理由!
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接口和Dictionary完成了此操作。
为动态属性实现ICustomTypeDescriptor:
最近,我需要将网格视图绑定到记录对象,该记录对象可以具有在运行时可以添加和删除的任意数量的属性。这是为了允许用户向结果集中添加新列,以输入其他数据集。
这可以通过将每个数据“行”作为字典来实现,其中键是属性名,值是可以存储指定行的属性值的字符串或类。当然,具有“字典列表”对象将无法绑定到网格。这是ICustomTypeDescriptor的来源。
通过为Dictionary创建包装类并使其遵循ICustomTypeDescriptor接口,可以覆盖返回对象属性的行为。
看一下下面的数据“行”类的实现:
/// <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包装类中的类型相同,否则强制转换将失败。创建描述符时,将传递密钥(属性名称),并用于查询字典以获取正确的值。
取自我的博客,网址为:
作为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);
}
}
}
通过此实现,当您使用新的键值对([]=如果字典中尚不存在)时,设置器将添加它们。
另外,对我来说properties是IDictionary,在构造函数中,我将其初始化为new SortedDictionary<string, string>()。
record[name_column] = DBConvert.To<string>(r[name_column]);在哪里record。我如何在客户端获得此价值?