Объединить несколько объектов C # - PullRequest
0 голосов
/ 06 июля 2019

У меня есть два таких объекта:

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

object obj2=new
            {
               Age=22                   
            }

и я хочу объединить их программно следующим образом:

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

Обратите внимание, что вполне возможно, что каждый класс может быть инициирован вместо класса Test, и я не знаю, какой класс в моем объекте, и я нашел эти объекты только во время выполнения.

Я прочитал те же вопросы в StackOverflow и обнаружил, что должен использовать Reflection, но все они знают классы Type, и я нашел его только во время выполнения. Как это можно сделать?

Ответы [ 2 ]

4 голосов
/ 06 июля 2019
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;
    }
}

Использование

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

Примечание , вам понадобится какой-то механизм для разрешения конфликтов свойств, если типы имеют одинаковые имена свойств.

1 голос
/ 07 июля 2019

На основании этого ответа: https://stackoverflow.com/a/3862241/9748260

Я немного изменил предоставленный построитель типов, чтобы он выглядел следующим образом:

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

Затем я создал метод для объединения объектов:

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

Для проверки результата:

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

Вывод:

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

Чтобы объяснить это вкратце, идея состоит в том, чтобы создать новый объект, который будет иметь свойства обоих объектов и скопировать их значения.

...