在运行时在现有类c#中动态添加属性

时间:2017-05-19 10:09:21

标签: c# .net windows reflection reflection.emit

我有一个用户界面,我们在表 Fields

中添加以下值
  • 产品名称
  • 产品编号
  • 产品代码

我有一个现有的 Product 类,其中包含一些现有的属性

public class Product
{
    public string ProductID { get; set; }
    //product in a product search listing
    public string StoreName { get; set; }
    public string SearchToken { get; set; }

}

我正在寻找一种方法,当用户在表中添加值时,它会在运行时(动态)在现有的类 Product 中添加属性 Fields

6 个答案:

答案 0 :(得分:4)

我不知道在运行时定义属性的方法,但是您实现所需的另一种可能性是在C#中使用称为ExpandoObject的动态对象。

首先需要声明动态对象,它在内部使用一种Dictionary,然后您可以将属性添加到它。

using System.Dynamic;
dynamic newobj = new ExpandoObject();

//I can add properties during compile time such as this
newobj.Test = "Yes";
newobj.JValue = 123;

//Or during runtime such as this (populated from two text boxes)
AddProperty(newobj, tbName.Text, tbValue.Text);

public void AddProperty(ExpandoObject expando, string name, object value)
{
    var exDict = expando as IDictionary<string, object>;
    if (exDict.ContainsKey(propertyName))
        exDict[propertyName] = propertyValue;
    else
    exDict.Add(propertyName, propertyValue);
}

我在这里的解决方案中使用过一次:Flattern child/parent data with unknown number of columns

但是这些来源可能更好地解释它; https://www.oreilly.com/learning/building-c-objects-dynamically

https://weblog.west-wind.com/posts/2012/feb/08/creating-a-dynamic-extensible-c-expando-object

https://docs.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/types/using-type-dynamic

但是,我不确定这是否真的会比使用简单的Dictionary<string, object>

真正优势

答案 1 :(得分:2)

定义:

public class Product
{
    public string ProductID { get; set; }
    //product in a product search listing
    public string StoreName { get; set; }
    public string SearchToken { get; set; }
    public Dictionary<string, object> Fields { get; set; }
}

用法:

var Prod = new Product();
Prod.Fields.Add("MyCustomFieldString", "my value here");
Prod.Fields.Add("MCustomFieldInt", 123);

MessageBox.Show(Prod.Fields[MyCustomFieldString].ToString());
MessageBox.Show(Prod.Fields[MCustomFieldInt].ToString());

答案 2 :(得分:0)

你需要澄清(也许对你自己)你究竟需要什么,或者我想说,你想要的。

编译代码后,将根据您的types \ methods \ fields生成元数据。有许多表(更具体地说是45个)来描述您的数据。

因此,如果要向某些类型添加新属性,则需要向元数据表添加新行。要添加新行,需要编译代码

那么Refelection.Emit是什么?是一种在您的应用中动态创建新数据的方法。有了这个你创建一个动态组件,动态类型,动态方法,添加一些IL,你去。但是,所有这些都是 in memory 。那不是你想要的。

所以,或使用Emit \ ExpandoObject动态数据,或通过将属性添加到代码并编译它来获取静态数据。

你还有一种在运行时编译方式之间的方式,它通过使用它来调用Mono.Cecil,你可以通过代码添加新的属性来输入,然后编译它和将新dll加载到AppDomain或只是关闭应用程序并重新午餐。它可以通过代码自动完成。没有魔力,它会将数据添加到程序集中的元数据表中,但它可以缩短手动执行此操作的方式。

但是我确定你知道更多选择。我使用的是Visual Studio,您可能听说过Edit and Continue(EnC)。此功能实际上是为您的正在运行的程序集编写新代码,即时将新数据(以及新数据)添加到元数据中。

所以,我不能告诉你你应该做什么,因为我不确切地知道你的情况是什么,并且有些人在他们不需要时需要极端的解决方案,但如果你真的必须选择将新数据添加到已经运行的程序集中,EnC可以是一个解决方案。要使用EnC,请查看Roslyn代码中的功能(您可能需要检查其测试以了解如何使用)。在那之后,看看@Josh Varty示例here

毕竟我写道,我真的(x3)不认为你需要为你需要的东西做些疯狂的事情。我确信有更好,更简单的方法。

答案 3 :(得分:0)

正如其他人指出的那样,这听起来不像您的情况要求这种行为。但是,您要求的 是可能的。使用System.Reflection.Emit,可以在运行时动态扩展基类,甚至在运行时将其保存为已编译的.dll。比较棘手的部分是弄清楚如何从生产代码中将新类部署到生产中,并在需要时覆盖以前的类。我会把那部分留给您解决。根据问题的其余部分,在为需要实现者扩展基类的框架创建自动测试时,我也有类似的需求,因此我建立了一个微型框架来实现这一点。您可以像这样使用它:

    public void TestTypeCreator()
    {
        //create and save new type
        object _newProduct = DynamicTypeCreator
            .Create("NewProduct", typeof(Product), @"C:\PROD")
            .AddPassThroughCtors()
            .AddProperty("ProductName", typeof(string))
            .FinishBuildingAndSaveType("NewProduct.dll")
            .GetConstructor(new Type[0] { })
            .Invoke(new object[0] { });

        //set ProductName value
        _newProduct.GetType().GetProperty("ProductName").SetValue(_newProduct, "Cool Item");

        //get ProductName value
        string _prodName = _newProduct.GetType().GetProperty("ProductName").GetValue(_newProduct).ToString();

        //get StoreName value
        string _storeName = _newProduct.GetType().GetProperty("StoreName").GetValue(_newProduct).ToString();
    }

由于我没有在任何地方发布此代码,因此将整个内容粘贴在下面。此“框架”非常有限,您可能需要针对特定​​目的对其进行添加/修改。来吧:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;

namespace ConsoleCommon
{
public interface IBaseObject
{
    IEmptyObject AddPassThroughCtors();
}
public interface IEmptyObject
{
    IAfterProperty AddProperty(string name, Type type);
}
public interface IAfterProperty : IEmptyObject, IFinishBuild
{
    IAfterAttribute AddPropertyAttribute(Type attrType, Type[] ctorArgTypes, params object[] ctorArgs);
}
public interface IAfterAttribute : IEmptyObject, IFinishBuild
{

}
public interface IFinishBuild
{
    Type FinishBuildingType();
    Type FinishBuildingAndSaveType(string assemblyFileName);
}
public static class DynamicTypeCreator
{
    public static IBaseObject Create(string className, Type parentType)
    {
        return new DynamicTypeCreatorBase().Create(className, parentType);
    }
    public static IBaseObject Create(string className, Type parentType, string dir)
    {
        return new DynamicTypeCreatorBase().Create(className, parentType, dir);
    }
}
public class PropertyBuilding
{
    public PropertyBuilding(PropertyBuilder propertyBuild, MethodBuilder getBuild, MethodBuilder setBuild)
    {
        propertyBuilder = propertyBuild;
        getBuilder = getBuild;
        setBuilder = setBuild;
    }
    public PropertyBuilder propertyBuilder { get; }
    public MethodBuilder getBuilder { get; }
    public MethodBuilder setBuilder { get; }
}
public class DynamicTypeCreatorBase : IBaseObject, IEmptyObject, IAfterProperty, IAfterAttribute
{
    TypeBuilder _tBuilder;
    List<PropertyBuilding> _propBuilds = new List<PropertyBuilding>();
    AssemblyBuilder _aBuilder;
    /// <summary>
    /// Begins creating type using the specified name.
    /// </summary>
    /// <param name="className">Class name for new type</param>
    /// <param name="parentType">Name of base class. Use object if none</param>
    /// <returns></returns>
    public IBaseObject Create(string className, Type parentType)
    {
        return Create(className, parentType, "");
    }
    /// <summary>
    /// Begins creating type using the specified name and saved in the specified directory.
    /// Use this overload to save the resulting .dll in a specified directory.
    /// </summary>
    /// <param name="className">Class name for new type</param>
    /// <param name="parentType">Name of base class. Use object if none</param>
    /// <param name="dir">Directory path to save .dll in</param>
    /// <returns></returns>
    public IBaseObject Create (string className, Type parentType, string dir)
    {
        _parentType = parentType;
        //Define type
        AssemblyName _name = new AssemblyName(className);
        if (string.IsNullOrWhiteSpace(dir))
        {
            _aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_name, AssemblyBuilderAccess.RunAndSave);
        }
        else _aBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(_name, AssemblyBuilderAccess.RunAndSave, dir);
        ModuleBuilder _mBuilder = _aBuilder.DefineDynamicModule(_name.Name, _name.Name + ".dll");
        _tBuilder = _mBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, parentType);
        return this;
    }
    /// <summary>
    /// Adds constructors to new type that match all constructors on base type.
    /// Parameters are passed to base type.
    /// </summary>
    /// <returns></returns>
    public IEmptyObject AddPassThroughCtors()
    {
        foreach(ConstructorInfo _ctor in _parentType.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
        {
            ParameterInfo[] _params = _ctor.GetParameters();
            Type[] _paramTypes = _params.Select(p => p.ParameterType).ToArray();
            Type[][] _reqModifiers = _params.Select(p => p.GetRequiredCustomModifiers()).ToArray();
            Type[][] _optModifiers = _params.Select(p => p.GetOptionalCustomModifiers()).ToArray();
            ConstructorBuilder _ctorBuild = _tBuilder.DefineConstructor(MethodAttributes.Public, _ctor.CallingConvention, _paramTypes, _reqModifiers, _optModifiers);
            for (int i = 0; i < _params.Length; i++)
            {
                ParameterInfo _param = _params[i];
                ParameterBuilder _prmBuild = _ctorBuild.DefineParameter(i + 1, _param.Attributes, _param.Name);
                if (((int)_param.Attributes & (int)ParameterAttributes.HasDefault) != 0) _prmBuild.SetConstant(_param.RawDefaultValue);

                foreach(CustomAttributeBuilder _attr in GetCustomAttrBuilders(_param.CustomAttributes))
                {
                    _prmBuild.SetCustomAttribute(_attr);
                }
            }

            //ConstructorBuilder _cBuilder = _tBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Any, argTypes);
            ILGenerator _ctorGen = _ctorBuild.GetILGenerator();
            _ctorGen.Emit(OpCodes.Nop);
            //arg0=new obj, arg1-arg3=passed params. Push onto stack for call to base class
            _ctorGen.Emit(OpCodes.Ldarg_0);
            for (int i = 1; i <= _params.Length; i++) _ctorGen.Emit(OpCodes.Ldarg, i);
            _ctorGen.Emit(OpCodes.Call, _ctor);
            _ctorGen.Emit(OpCodes.Ret);
        }
        return this;
    }
    /// <summary>
    /// Adds a new property to type with specified name and type.
    /// </summary>
    /// <param name="name">Name of property</param>
    /// <param name="type">Type of property</param>
    /// <returns></returns>
    public IAfterProperty AddProperty(string name, Type type)
    {
        //base property
        PropertyBuilder _pBuilder = _tBuilder.DefineProperty(name, PropertyAttributes.None, type, new Type[0] { });
        //backing field
        FieldBuilder _fBuilder = _tBuilder.DefineField($"m_{name}", type, FieldAttributes.Private);

        //get method
        MethodAttributes _propAttrs = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
        MethodBuilder _getBuilder = _tBuilder.DefineMethod($"get_{name}", _propAttrs, type, Type.EmptyTypes);
        ILGenerator _getGen = _getBuilder.GetILGenerator();
        _getGen.Emit(OpCodes.Ldarg_0);
        _getGen.Emit(OpCodes.Ldfld, _fBuilder);
        _getGen.Emit(OpCodes.Ret);

        //set method
        MethodBuilder _setBuilder = _tBuilder.DefineMethod($"set_{name}", _propAttrs, null, new Type[] { type });
        ILGenerator _setGen = _setBuilder.GetILGenerator();
        _setGen.Emit(OpCodes.Ldarg_0);
        _setGen.Emit(OpCodes.Ldarg_1);
        _setGen.Emit(OpCodes.Stfld, _fBuilder);
        _setGen.Emit(OpCodes.Ret);

        _propBuilds.Add(new PropertyBuilding(_pBuilder, _getBuilder, _setBuilder));
        return this;
    }
    /// <summary>
    /// Adds an attribute to a property just added.
    /// </summary>
    /// <param name="attrType">Type of attribute</param>
    /// <param name="ctorArgTypes">Types of attribute's cstor parameters</param>
    /// <param name="ctorArgs">Values to pass in to attribute's cstor. Must match in type and order of cstorArgTypes parameter</param>
    /// <returns></returns>
    public IAfterAttribute AddPropertyAttribute(Type attrType, Type[] ctorArgTypes, params object[] ctorArgs)
    {
        if (ctorArgTypes.Length != ctorArgs.Length) throw new Exception("Type count must match arg count for attribute specification");
        ConstructorInfo _attrCtor = attrType.GetConstructor(ctorArgTypes);
        for (int i = 0; i < ctorArgTypes.Length; i++)
        {
            CustomAttributeBuilder _attrBuild = new CustomAttributeBuilder(_attrCtor, ctorArgs);
            _propBuilds.Last().propertyBuilder.SetCustomAttribute(_attrBuild);
        }
        return this;
    }
    /// <summary>
    /// Completes building type, compiles it, and returns the resulting type
    /// </summary>
    /// <returns></returns>
    public Type FinishBuildingType()
    {
        foreach(var _pBuilder in _propBuilds)
        {
            _pBuilder.propertyBuilder.SetGetMethod(_pBuilder.getBuilder);
            _pBuilder.propertyBuilder.SetSetMethod(_pBuilder.setBuilder);
        }

        Type _paramsType = _tBuilder.CreateType();
        return _paramsType;
    }
    /// <summary>
    /// Completes building type, compiles it, saves it, and returns the resultying type.
    /// Assembly is saved in the calling assembly's directory or in the dir specified in the Create method.
    /// </summary>
    /// <param name="assemblyFileName">Filename of the assembly</param>
    /// <returns></returns>
    public Type FinishBuildingAndSaveType(string assemblyFileName)
    {
        Type _newType = FinishBuildingType();
        Save(assemblyFileName);
        return _newType;
    }
    #region Helpers
    private CustomAttributeBuilder[] GetCustomAttrBuilders(IEnumerable<CustomAttributeData> customAttributes)
    {
        return customAttributes.Select(attribute => {
            object[] attributeArgs = attribute.ConstructorArguments.Select(a => a.Value).ToArray();
            PropertyInfo[] namedPropertyInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<PropertyInfo>().ToArray();
            object[] namedPropertyValues = attribute.NamedArguments.Where(a => a.MemberInfo is PropertyInfo).Select(a => a.TypedValue.Value).ToArray();
            FieldInfo[] namedFieldInfos = attribute.NamedArguments.Select(a => a.MemberInfo).OfType<FieldInfo>().ToArray();
            object[] namedFieldValues = attribute.NamedArguments.Where(a => a.MemberInfo is FieldInfo).Select(a => a.TypedValue.Value).ToArray();
            return new CustomAttributeBuilder(attribute.Constructor, attributeArgs, namedPropertyInfos, namedPropertyValues, namedFieldInfos, namedFieldValues);
        }).ToArray();
    }
    /// <summary>
    /// Requires admin privileges. 
    /// To save in a specified dir, use the Create overload that requires a 'dir' parameter.
    /// </summary>
    /// <param name="assemblyFileName"></param>
    private void Save(string assemblyFileName)
    {
        string _assemblyName = assemblyFileName;
        if (!Path.HasExtension(assemblyFileName) || Path.GetExtension(assemblyFileName).ToLower() != ".dll")
            _assemblyName += ".dll";
        _aBuilder.Save(_assemblyName);
    }
    #endregion
}
}

答案 4 :(得分:0)

将属性添加到现有类中,如下所示:

 class MyClass
  {
    public int Id { get; set; }// Existing property
    public List<dynamic> Information { get; set; } 
    // Added above property to handle new properties which will come dynamically  
  }

       //-------- While Implementing ----
        MyClass obj = new MyClass();
        obj.Id = 1111; // Existing Property
        obj.Information = new List<dynamic>();

        obj.Information.Add(new ExpandoObject());
        obj.Information[0].Name= "Gyan"; // New Property 
        obj.Information[0].Age = 22; // New Property 

答案 5 :(得分:0)

我发现使用 DynamicObject 类对于在运行时动态添加属性很有用。