1

I have two objects like this:

object obj1=new
            {
               ID=1,
               Title="text",
               Test= new Test(){ 
                        Number=20, IsSomething=false
                     }
            }

object obj2=new
            {
               Age=22                   
            }

and I want to have merged of them programmatically like this:

object obj3=new
            {
               ID=1,
               Title="text",
               Test= new Test(){ 
                        Number=20, IsSomething=false
                     },
               Age=22                   
            }

Note that it is possible each class can be initiated instead of Test class and I Don't know which class in the object I have and I found these objects just in runtime.

I read the same questions in StackOverflow and found that I should use Reflection but all of them know Type of classes and I found it just in Runtime. How it is possible to do?

wakiwiki
  • 231
  • 4
  • 13
  • 1
    What are you trying to accomplish? (Please add more details.) In some languages you might use multiple inheritance but C# does not support it - https://stackoverflow.com/questions/2456154/does-c-sharp-support-multiple-inheritance – Dave S Jul 06 '19 at 19:59
  • 1
    You could use reflection to both grab the properties and values of the two source objects as well as dynamically create a new type with the combined set of properties. However, anonymous objects are usually not a good fit for this kind of thing, may I ask what you're ultimately trying to accomplish here? It's very unusual for code to use anonymous objects in such a way that you couldn't simply do `new { ID = ..., Title = ..., Test = ..., Age = ... }`. – Lasse V. Karlsen Jul 06 '19 at 20:01
  • You are looking for the dynamic keyword and ExpandoObject. These allows you to add and remove properties dynamically. Very much like Javascript. Research examples to find out how they work. Reflection is a built-in feature of ExpanoObject. – TamusJRoyce Jul 06 '19 at 20:19
  • 1
    @TamusJRoyce Exactly true, I need to do like JS. Can you tell me more about it? thank you – wakiwiki Jul 06 '19 at 20:39

2 Answers2

7
static class MergeExtension
{
    public static ExpandoObject Merge<TLeft, TRight>(this TLeft left, TRight right)
    {
        var expando = new ExpandoObject();
        IDictionary<string, object> dict = expando;
        foreach (var p in typeof(TLeft).GetProperties())
            dict[p.Name] = p.GetValue(left);
        foreach (var p in typeof(TRight).GetProperties())
            dict[p.Name] = p.GetValue(right);
        return expando;
    }
}

Usage

var obj1 = new
{
    ID = 1,
    Title = "text",
    Test = new Test()
    {
        Number = 20,
        IsSomething = false
    }
};

var obj2 = new
{
    Age = 22
};

dynamic obj3 = obj1.Merge(obj2);

Console.WriteLine(obj1.ID.Equals(obj3.ID)); // True
Console.WriteLine(obj1.Title.Equals(obj3.Title)); // True
Console.WriteLine(obj1.Test.Equals(obj3.Test)); // True
Console.WriteLine(obj2.Age.Equals(obj3.Age)); // True

Note, you would need some mechanism to resolve property conflicts, if types have same property names.

CSDev
  • 3,177
  • 6
  • 19
  • 37
  • 1
    `ExpandoObject` is not the same as an anonymous object, and what happens if either the left or the right side is already an `ExpandoObject` (upvoted though, probably the simplest solution to the question) – Lasse V. Karlsen Jul 06 '19 at 20:53
  • 5
    @ Lasse Vågsæther Karlsen, thanks! Good point. There are lots of nuances to think about. It's just a thing to start with. – CSDev Jul 06 '19 at 20:57
  • @Alex Thank you but I test it and returns nothing – wakiwiki Jul 06 '19 at 21:06
  • 2
    "returns nothing", how did you verify? Given your code it gave me a perfect `ExpandoObject` with all the properties and values intact. How did you arrive at "returns nothing"? – Lasse V. Karlsen Jul 06 '19 at 21:38
  • 1
    @alex Never tried using an extension using a generic function. Very interesting! – TamusJRoyce Jul 06 '19 at 23:01
  • @Waki, _"returns nothing"_ looks confusing. – CSDev Jul 07 '19 at 11:03
  • Here I agree with @Lasse Vågsæther Karlsen. – CSDev Jul 07 '19 at 11:04
  • @Alex this part "foreach (var p in typeof(TLeft).GetProperties())" is not correctly working I tested it and it returns zero counts of the property while my object contains some property, when I use " Type type = obj.GetType();" and then "IList props = new List(type.GetProperties())" and doing loop on "props" it is working truely and "props" has correct count of properties – wakiwiki Jul 08 '19 at 21:45
  • @Waki, it is weird and interesting. `left.GetType()` should return `typeof(TLeft)`. Check that `typeof(Left) == left.GetType()` is `true`. – CSDev Jul 09 '19 at 10:35
3

Based on this answer: https://stackoverflow.com/a/3862241/9748260

I modified the provided type builder a little to look like this:

public static class MyTypeBuilder
{
  public static Type CompileResultType(List<PropertyInfo> yourListOfFields, string typeName)
  {
    TypeBuilder tb = GetTypeBuilder(typeName);
    ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);

    // NOTE: assuming your list contains Field objects with fields FieldName(string) and FieldType(Type)
    foreach (var field in yourListOfFields)
      CreateProperty(tb, field.Name, field.PropertyType);

    Type objectType = tb.CreateType();
    return objectType;
  }

  private static TypeBuilder GetTypeBuilder(string typeSignature)
  {
    var an = new AssemblyName(typeSignature);
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
    ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
    TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
            TypeAttributes.Public |
            TypeAttributes.Class |
            TypeAttributes.AutoClass |
            TypeAttributes.AnsiClass |
            TypeAttributes.BeforeFieldInit |
            TypeAttributes.AutoLayout,
            null);
    return tb;
  }

  private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType)
  {
    FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);

    PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
    MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
    ILGenerator getIl = getPropMthdBldr.GetILGenerator();

    getIl.Emit(OpCodes.Ldarg_0);
    getIl.Emit(OpCodes.Ldfld, fieldBuilder);
    getIl.Emit(OpCodes.Ret);

    MethodBuilder setPropMthdBldr =
        tb.DefineMethod("set_" + propertyName,
          MethodAttributes.Public |
          MethodAttributes.SpecialName |
          MethodAttributes.HideBySig,
          null, new[] { propertyType });

    ILGenerator setIl = setPropMthdBldr.GetILGenerator();
    Label modifyProperty = setIl.DefineLabel();
    Label exitSet = setIl.DefineLabel();

    setIl.MarkLabel(modifyProperty);
    setIl.Emit(OpCodes.Ldarg_0);
    setIl.Emit(OpCodes.Ldarg_1);
    setIl.Emit(OpCodes.Stfld, fieldBuilder);

    setIl.Emit(OpCodes.Nop);
    setIl.MarkLabel(exitSet);
    setIl.Emit(OpCodes.Ret);

    propertyBuilder.SetGetMethod(getPropMthdBldr);
    propertyBuilder.SetSetMethod(setPropMthdBldr);
  }
}

Then I created a method to merge the objects:

public static object Merge(object obj1, object obj2, string newTypeName)
{
  var obj1Properties = obj1.GetType().GetProperties();
  var obj2Properties = obj2.GetType().GetProperties();
  var properties = obj1Properties.Concat(obj2Properties).ToList();
  Type mergedType = MyTypeBuilder.CompileResultType(properties, newTypeName);
  object mergedObject = Activator.CreateInstance(mergedType);
  var mergedObjectProperties = obj2.GetType().GetProperties();

  foreach(var property in obj1Properties)
  {
    mergedObject.GetType().GetProperty(property.Name).SetValue(mergedObject, obj1.GetType().GetProperty(property.Name).GetValue(obj1, null) , null);
  }

  foreach(var property in obj2Properties)
  {
    mergedObject.GetType().GetProperty(property.Name).SetValue(mergedObject, obj2.GetType().GetProperty(property.Name).GetValue(obj2, null) , null);
  }

  return mergedObject;
}

To test the result:

object obj1 = new
{
  ID = 1,
  Title = "text",
  Test = new
  {
    Number = 20,
    IsSomething = false
  }
};

object obj2 = new
{
  Age = 22
};
object merged = Merge(obj1, obj2, "merged");

foreach(var x in merged.GetType().GetProperties())
{
  Console.WriteLine($"{x.Name} = {x.GetValue(merged, null)}");
}

Console.ReadLine();

The output is:

ID = 1
Title = text
Test = { Number = 20, IsSomething = False }
Age = 22

To explain this briefly, the idea is to create a new object which have the properties of both objects and copy their values.

Sohaib Jundi
  • 1,576
  • 2
  • 7
  • 15