我有一个用户界面,我们在表 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
答案 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 类对于在运行时动态添加属性很有用。