【问题标题】:Add a property at runtime to an existing object by using propertyBuilder使用 propertyBuilder 在运行时将属性添加到现有对象
【发布时间】:2012-11-11 13:03:00
【问题描述】:

一个对象有一些属性,现在在运行时——当一个条件满足时.. 我想给这个对象添加新的属性。

“DynamicObject”无法使用,因为我不会事先知道属性名称

我来了 PropertyBuilder http://msdn.microsoft.com/en-us/library/system.reflection.emit.propertybuilder.aspx

但我找不到有关如何使用 propertyBuilder 向已定义的现有类的现有对象添加属性的帮助。

【问题讨论】:

    标签: c# dynamic reflection .net-4.0 reflection.emit


    【解决方案1】:

    您不能在运行时向对象或类型添加 真实(反射)属性。

    如果这里的上下文是数据绑定,那么您可以通过实现ICustomTypeDescriptorTypeDescriptionProviderTypeConverterITypedList 中的一项或多项来实现所有人工属性 - 并提供您自己的PropertyDescriptors 以​​获得额外的属性。

    • ICustomTypeDescriptor 是每个对象并绑定到该对象
    • TypeDescriptionProvider 是每个对象或每个类型的,并且与对象独立
    • TypeConverter 是每个类型的,特别是由 PropertyGrid 使用
    • ITypedList 被列表 (IList) 用来描述子对象的属性

    例子:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Forms;
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            FooConverter.AddProperty("Time", typeof(DateTime));
            FooConverter.AddProperty("Age", typeof(int));
            using (var grid = new PropertyGrid { Dock = DockStyle.Fill, SelectedObject = new Foo() })
            using (var form = new Form { Controls = { grid } })
            {
                Application.Run(form);
            }
        }
    }
    class FooConverter : ExpandableObjectConverter
    {
        private static readonly List<Tuple<string, Type>> customProps = new List<Tuple<string, Type>>();
        public static void AddProperty(string name, Type type)
        {
            lock (customProps) customProps.Add(Tuple.Create(name, type));
        }
        public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, System.Attribute[] attributes)
        {
            var orig = base.GetProperties(context, value, attributes);
            lock(customProps)
            {
                if(customProps.Count == 0) return orig;
    
                PropertyDescriptor[] props = new PropertyDescriptor[orig.Count + customProps.Count];
                orig.CopyTo(props, 0);
                int i = orig.Count;
                foreach (var prop in customProps)
                {
                    props[i++] = new SimpleDescriptor(prop.Item1, prop.Item2);
                }
                return new PropertyDescriptorCollection(props);
            }
        }
        class SimpleDescriptor : PropertyDescriptor
        {
            private readonly Type type;
            public SimpleDescriptor(string name, Type type)
                : base(name, new Attribute[0])
            {
                this.type = type;
            }
            public override Type PropertyType { get { return type;} }
            public override bool SupportsChangeEvents { get { return false; } }
            public override void ResetValue(object component) { SetValue(component, null); }
            public override bool CanResetValue(object component) { return true; }
            public override bool ShouldSerializeValue(object component) { return GetValue(component) != null; }
            public override bool IsReadOnly { get { return false; } }
            public override Type ComponentType { get { return typeof(Foo); } }
            public override object GetValue(object component) { return ((Foo)component).GetExtraValue(Name); }
            public override void SetValue(object component, object value) { ((Foo)component).SetExtraValue(Name, value); }
            public override string Category { get { return "Extra values"; } }
        }
    }
    [TypeConverter(typeof(FooConverter))]
    public class Foo
    {
        Dictionary<string, object> extraValues;
        internal object GetExtraValue(string name)
        {
            object value;
            if (extraValues == null || !extraValues.TryGetValue(name, out value)) value = null;
            return value;
        }
        internal void SetExtraValue(string name, object value)
        {
            if (extraValues == null && value != null) extraValues = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);
            if (value == null) extraValues.Remove(name);
            else extraValues[name] = value;
        }
        public int Id { get; set; }
        public string Name { get; set; }
    }
    

    【讨论】:

    • @KhannaB7 如果你在PropertyGrid 中显示它,那么TypeConverter 是最简单的选择——比实现ICustomTypeDescriptorTypeDescriptionProvider 容易得多——你只需继承ExpandableObjectConverter 并覆盖@ 987654338@,并使用TypeConverterAttribute 将您的转换器链接到您的类型。
    • Grawell ♦ 我的意图是:向该对象添加一个属性(分配一个属性 - 自定义 UIEditor),在 propertyGrid 中显示该属性,对其进行编辑,甚至为其他一些属性获取该属性的编辑值操作..有可能吗???
    • 是的...按照我上面所说的去做
    • @KhannaB7 完整示例添加
    • 你的解决方案很棒。感谢您的帮助。
    【解决方案2】:

    检查此地址 https://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes(v=vs.110).aspx

    using System;
    using System.Threading;
    using System.Reflection;
    using System.Reflection.Emit;
    
    class EmitWriteLineDemo {
    
       public static Type CreateDynamicType() {       
           Type[] ctorParams = new Type[] {typeof(int),
                       typeof(int)};
    
           AppDomain myDomain = Thread.GetDomain();
           AssemblyName myAsmName = new AssemblyName();
           myAsmName.Name = "MyDynamicAssembly";
    
           AssemblyBuilder myAsmBuilder = myDomain.DefineDynamicAssembly(
                          myAsmName, 
                          AssemblyBuilderAccess.Run);
    
           ModuleBuilder pointModule = myAsmBuilder.DefineDynamicModule("PointModule",
                                        "Point.dll");
    
           TypeBuilder pointTypeBld = pointModule.DefineType("Point",
                                      TypeAttributes.Public);
    
           FieldBuilder xField = pointTypeBld.DefineField("x", typeof(int),
                                                          FieldAttributes.Public);
           FieldBuilder yField = pointTypeBld.DefineField("y", typeof(int), 
                                                          FieldAttributes.Public);
    
    
           Type objType = Type.GetType("System.Object"); 
           ConstructorInfo objCtor = objType.GetConstructor(new Type[0]);
    
           ConstructorBuilder pointCtor = pointTypeBld.DefineConstructor(
                                       MethodAttributes.Public,
                                       CallingConventions.Standard,
                                       ctorParams);
           ILGenerator ctorIL = pointCtor.GetILGenerator();
    
    
           // First, you build the constructor.
           ctorIL.Emit(OpCodes.Ldarg_0);
           ctorIL.Emit(OpCodes.Call, objCtor);
           ctorIL.Emit(OpCodes.Ldarg_0);
           ctorIL.Emit(OpCodes.Ldarg_1);
           ctorIL.Emit(OpCodes.Stfld, xField); 
           ctorIL.Emit(OpCodes.Ldarg_0);
           ctorIL.Emit(OpCodes.Ldarg_2);
           ctorIL.Emit(OpCodes.Stfld, yField); 
           ctorIL.Emit(OpCodes.Ret); 
    
           //  Now, you'll build a method to output some information on the
           // inside your dynamic class. This method will have the following
           // definition in C#:
        //  public void WritePoint()
    
           MethodBuilder writeStrMthd = pointTypeBld.DefineMethod(
                                         "WritePoint", 
                                 MethodAttributes.Public,
                                                 typeof(void), 
                                                 null);
    
    
           ILGenerator writeStrIL = writeStrMthd.GetILGenerator();
    
           // The below ILGenerator created demonstrates a few ways to create
           // string output through STDIN. 
    
           // ILGenerator.EmitWriteLine(string) will generate a ldstr and a 
           // call to WriteLine for you.
    
           writeStrIL.EmitWriteLine("The value of this current instance is:");
    
           // Here, you will do the hard work yourself. First, you need to create
           // the string we will be passing and obtain the correct WriteLine overload
           // for said string. In the below case, you are substituting in two values,
           // so the chosen overload is Console.WriteLine(string, object, object).
    
           String inStr = "({0}, {1})";
           Type[] wlParams = new Type[] {typeof(string),
                         typeof(object),
                         typeof(object)};
    
           // We need the MethodInfo to pass into EmitCall later.
    
           MethodInfo writeLineMI = typeof(Console).GetMethod(
                                "WriteLine",
                            wlParams);
    
           // Push the string with the substitutions onto the stack.
           // This is the first argument for WriteLine - the string one. 
    
           writeStrIL.Emit(OpCodes.Ldstr, inStr);
    
           // Since the second argument is an object, and it corresponds to
           // to the substitution for the value of our integer field, you 
           // need to box that field to an object. First, push a reference
           // to the current instance, and then push the value stored in
           // field 'x'. We need the reference to the current instance (stored
           // in local argument index 0) so Ldfld can load from the correct
           // instance (this one).
    
           writeStrIL.Emit(OpCodes.Ldarg_0);
           writeStrIL.Emit(OpCodes.Ldfld, xField);
    
           // Now, we execute the box opcode, which pops the value of field 'x',
           // returning a reference to the integer value boxed as an object.
    
           writeStrIL.Emit(OpCodes.Box, typeof(int));
    
           // Atop the stack, you'll find our string inStr, followed by a reference
           // to the boxed value of 'x'. Now, you need to likewise box field 'y'.
    
           writeStrIL.Emit(OpCodes.Ldarg_0);
           writeStrIL.Emit(OpCodes.Ldfld, yField);
           writeStrIL.Emit(OpCodes.Box, typeof(int));
    
           // Now, you have all of the arguments for your call to
           // Console.WriteLine(string, object, object) atop the stack:
           // the string InStr, a reference to the boxed value of 'x', and
           // a reference to the boxed value of 'y'.
    
           // Call Console.WriteLine(string, object, object) with EmitCall.
    
           writeStrIL.EmitCall(OpCodes.Call, writeLineMI, null);
    
           // Lastly, EmitWriteLine can also output the value of a field
           // using the overload EmitWriteLine(FieldInfo).
    
           writeStrIL.EmitWriteLine("The value of 'x' is:");
           writeStrIL.EmitWriteLine(xField);
           writeStrIL.EmitWriteLine("The value of 'y' is:");
           writeStrIL.EmitWriteLine(yField);
    
           // Since we return no value (void), the the ret opcode will not
           // return the top stack value.
    
           writeStrIL.Emit(OpCodes.Ret);
    
           return pointTypeBld.CreateType();
    
       }
    
       public static void Main() {
    
          object[] ctorParams = new object[2];
    
          Console.Write("Enter a integer value for X: "); 
          string myX = Console.ReadLine();
          Console.Write("Enter a integer value for Y: "); 
          string myY = Console.ReadLine();
    
          Console.WriteLine("---");
    
          ctorParams[0] = Convert.ToInt32(myX);
          ctorParams[1] = Convert.ToInt32(myY);
    
          Type ptType = CreateDynamicType();
    
          object ptInstance = Activator.CreateInstance(ptType, ctorParams);
          ptType.InvokeMember("WritePoint",
                  BindingFlags.InvokeMethod,
                  null,
                  ptInstance,
                  new object[0]);
       }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-10
      • 1970-01-01
      相关资源
      最近更新 更多