Add properties dynamically at run time in existing class c#
Asked Answered
A

6

7

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

Aground answered 19/5, 2017 at 10:9 Comment(5)
You cant modify the class definition at runtime unless you get into some very complex stuff and write some partial classes, compile into a dll at runtime and load them into memory. Sounds like you just need a Dictionary<string, object> so you can store values and give them a name.Intelsat
Actually ignore my previous statement about compiling partial classes at runtime, thats not even possible either.Intelsat
Ok Thanks CathalMFAground
I have tried with System.Reflection.Emit but not success.Aground
@Aground You can't do that. How did you envision using these added properties? The Dictionary idea is best.Refreshment
D
11

I don't know a way of defining properties during runtime, but another possibiity for you to achieve what you need is to use a dynamic object in C# known as ExpandoObject.

You first need to declare your dynamic object, it uses a kind of Dictionary internally so you can then add your properties to it.

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 propertyName, object propertyValue)
{
    var exDict = expando as IDictionary<string, object>;
    if (exDict.ContainsKey(propertyName))
        exDict[propertyName] = propertyValue;
    else
    exDict.Add(propertyName, propertyValue);
}

I have used it once in a solution here: Flattern child/parent data with unknown number of columns

But these sources can probably explain it better; 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://learn.microsoft.com/en-us/dotnet/articles/csharp/programming-guide/types/using-type-dynamic

But, I'm not sure that this would really offer you any real advantages over using a simple Dictionary<string, object>

Dakar answered 19/5, 2017 at 11:32 Comment(2)
Ok jason.kaisersmith thanks for your reply . I am not looking like ExpandoObject because we can use this ExpandoObject object at run time only. But I want the newly added property will exist permanently in my class as properties . In future we will use this as class properties . If one use added a new fields in table then it automatically add as property in my Product class alsoAground
I haven't actually tried this, but one reason this may be better than a Dictionary property is for JSON serialization. If you want a flat JSON namespace...Sovereign
A
5

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
}
}
Antineutron answered 10/11, 2018 at 1:4 Comment(0)
I
4

Definition:

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

Usage:

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());
Intelsat answered 19/5, 2017 at 10:44 Comment(3)
Ok Thanks CathalMF but I am looking as this new added properties will exist as permanent properties in product classAground
@Aground Permanant properties will require you to change the code, which means compiling and replacing dlls at runtime. This seems insane for simply adding a new search column.Intelsat
@Enerccio Imrove the answer or downvote it if the answer is not helpful.Fortenberry
S
0

You need to clarify (maybe to yourself) what exactly do you need, or I'll say, do you want.

After you compile your code, metadata is generated based on your types\methods\fields. There are many tables (45 to be more specific) to describe your data.

So, If you want to add new properties to some type, you need to add new rows to metadata tables. To add new rows, you need to compile code.

So what is Refelection.Emit? Is a way to dynamically create new data in your app. With this you create a dynamic assembly, dynamic type, dynamic method, add some IL and there you go. But, all of this is in memory. and that not what you want.

So, or dynamically data using Emit \ ExpandoObject, or statically data by adding the properties to code and compile it.

You have also a way of between the run time way to compile way, it's call Mono.Cecil by using it, you can, by code, add the new properties to type, then compile it and load the new dll to your AppDomain or just shutdown the app and re lunch it. It can be automatically and just by code. There is no magic, it will add data to the metadata tables in your assembly, but it can short the way of doing that manually.

But there is a more option that I'm sure you know about it. I you are using Visual Studio, you probably heard about Edit and Continue (EnC). This feature is actually write new code to your running assembly, on the fly add the new data (and only the new) to the metadata.

So, I can't tell you what you should do because I don't know exactly what is your case, and there is cases that people want extreme solution when they not need them, but if you really must the option to add new data to your already running assembly, EnC can be a solution. To use EnC take a look on the feature in Roslyn code (you might want to check their test to understand how to work with). After that, take a look on @Josh Varty example here.

After all I wrote, I really (x3) don't think you need to make something crazy for what you need. I'm sure there is a better, simple way to do it.

Simms answered 1/6, 2017 at 13:39 Comment(1)
Ok Thanks Dudi Keleti - i have used ExpandoObjexctAground
S
0

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

Snaggy answered 17/2, 2021 at 1:27 Comment(0)
B
-1

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 
Bolling answered 24/11, 2018 at 8:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.