使用propertyBuilder在运行时将属性添加到现有对象

时间:2012-11-23 07:00:49

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

对象有一些属性,现在在运行时 - 满足条件时.. 我想为此对象添加新属性。

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

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

但我无法找到有关如何使用propertiesBuilder为已定义现有类的现有对象添加属性的帮助。

2 个答案:

答案 0 :(得分:4)

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

如果此处的上下文是数据绑定,那么您可以通过实施ICustomTypeDescriptorTypeDescriptionProviderTypeConverter中的一个或多个来实现所有人工属性, ITypedList - 并为额外的属性提供您自己的PropertyDescriptor

  • 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; }
}

答案 1 :(得分:1)

检查此地址 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]);
   }
}