Add properties dynamically at run time in existing class c#

Viewed 29564

I have a UI from where we are adding following values in a table Fields

  • ProductName
  • ProductId
  • ProductCode

I have a existing class Product with some existing properties

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

}

I am looking for a method which will add properties in existing class Product at run time (dynamically) when user adds values in a table Fields

6 Answers

As others have pointed out, it doesn't sound like your scenario calls for this behavior. However, what you're asking for is possible. Using System.Reflection.Emit, you can dynamically extend your base class at runtime and even save it at runtime as a compiled .dll. The trickier part would be to figure out how to deploy your new class to production from your production code and overwrite your previous class, if that's needed. I'll leave that part up to you to figure out. As per the rest of your question, I had a similar need when creating automated testing for a framework that requires implementers to extend base classes, so I built a mini framework to do this. You can use it like this:

    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();
    }

Since I haven't published this code anywhere, I'll paste the whole thing below. This "framework" is very limited and you might have to add to it/modify it for your specific purpose. Here ya go:

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

I found using DynamicObject Class useful for adding properties dynamically during runtime.

Add a property to your Existing Class like below :

 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 
Related