Merging anonymous types
Asked Answered
O

4

44

How can I merge two anonymous types, so that the result contains the properties of both source objects?

var source1 = new
{
    foo = "foo",
    bar = "bar"
}

var source2 = new
{
    baz = "baz"
}

var merged = Merge(source1, source2) // <-- here's where the magic should happen

// merged: 
// {
//      foo = "foo",
//      bar = "bar",
//      baz = "baz"
// }
Overtrade answered 19/7, 2011 at 22:38 Comment(3)
If you're using C# 4, use dynamic for creating objects with dynamic members.Yun
@Dave, can you post the method you came up with as answer? It would be more visible. I liked it, I want to upvote it. :)Sb
@BrunoLM, just posted my solution. Go vote for it :-)Overtrade
O
47

So here's, what I finally came up with (inspired by @BlueMonkMN's answer):

public dynamic Merge(object item1, object item2)
{
    if (item1 == null || item2 == null)
        return item1 ?? item2 ?? new ExpandoObject();

    dynamic expando = new ExpandoObject();
    var result = expando as IDictionary<string, object>;
    foreach (System.Reflection.PropertyInfo fi in item1.GetType().GetProperties())
    {
        result[fi.Name] = fi.GetValue(item1, null);
    }
    foreach (System.Reflection.PropertyInfo fi in item2.GetType().GetProperties())
    {
        result[fi.Name] = fi.GetValue(item2, null);
    }
    return result;
}
Overtrade answered 23/7, 2011 at 19:21 Comment(4)
You might want to add: .Where(x => x.CanRead) to your GetProperties() call to avoid the case where you have write only properties.Clair
@davehauser, did you find to be performant? I would like to do this several times per page.Apogee
@Bomboca it's still worth the CanRead as the method's signature does not prevent non-anonymous objects.Walters
is there vb.net version ?Hayseed
E
15
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;

namespace ConsoleApplication1
{
  class Program
  {
     static void Main(string[] args)
     {
        var source1 = new
        {
            foo = "foo",
            bar = "bar"
        };

        var source2 = new
        {
           baz = "baz"
        };

        dynamic merged = Merge(source1, source2);

        Console.WriteLine("{0} {1} {2}", merged.foo, merged.bar, merged.baz);
     }

     static MergedType<T1, T2> Merge<T1, T2>(T1 t1, T2 t2)
     {
        return new MergedType<T1, T2>(t1, t2);
     }
  }

  class MergedType<T1, T2> : DynamicObject
  {
     T1 t1;
     T2 t2;
     Dictionary<string, object> members = new Dictionary<string, object>(StringComparer.InvariantCultureIgnoreCase);

     public MergedType(T1 t1, T2 t2)
     {
        this.t1 = t1;
        this.t2 = t2;
        foreach (System.Reflection.PropertyInfo fi in typeof(T1).GetProperties())
        {
           members[fi.Name] = fi.GetValue(t1, null);
        }
        foreach (System.Reflection.PropertyInfo fi in typeof(T2).GetProperties())
        {
           members[fi.Name] = fi.GetValue(t2, null);
        }
     }

     public override bool TryGetMember(GetMemberBinder binder, out object result)
     {
        string name = binder.Name.ToLower();
        return members.TryGetValue(name, out result);
     }
  }
}
Episiotomy answered 19/7, 2011 at 23:13 Comment(3)
Thanks again. Played around a bit with this. Any reason why you make MergedType generic? It works as well with object for the sources (static MergedType Merge(object item1, object item2) and then ... fi in item1.GetType().GetProperties(). I came then back to ExpandoObject, which seems to provide a similar solution, but with less code. I added my final solution to my question.Overtrade
No, I was thinking about that after I posted and realized that my initial attempts at making a more strongly-typed solution were futile and using templates was probably entirely unnecessary.Episiotomy
However, if you had any reason to still want to be able to access the members of T1 and T2 via MergedType, you could probably do it if you kept the templates. Intellisense would probably be able to show you that MergedType had a property of type T1 if you created one, and what it's members were.Episiotomy
A
11

You can't, if what you're expecting is a single object where you can access the properties in a compile-time type-safe way (as opposed to a result which is purely dynamic at execution time). The closest you could come would be:

var merged = Tuple.Create(source1, source2);

Console.WriteLine(merged.Item1.foo);
Console.WriteLine(merged.Item1.bar);
Console.WriteLine(merged.Item2.baz);

Bear in mind that anonymous types are created at compile-time. It's not like they're "dynamic" types. You could use ExpandoObject in .NET 4 for that, but it's not quite the same as an anonymous type with all the relevant properties in.

Allopath answered 19/7, 2011 at 22:43 Comment(5)
So, that's why I didn't find the answer in your book... :-)Overtrade
Is it possible to convert an anonymous type to an ExpandoObject? So I could merge them (cast as IDictionary<string, object> should do). If the output is an ExpandoObject, that would also work in my case.Overtrade
Hey, actally you can! Look at the second answer I posted (much better than my first) :)Episiotomy
Oh, I see now that ExpandoObject is very similar (and more appropriate) that DynamicObject, which is what I found.Episiotomy
Ok, I chose BlueMonkMN's answer. Although Jon answered my concrete question correctly, BlueMonkMN led me to a viable solution.Overtrade
R
4

The following works in .NET 3.5 (and probably 2.0 as well). I modified davehauser's answer.

    public static object MergeJsonData(object item1, object item2)
    {
        if (item1 == null || item2 == null)
            return item1 ?? item2 ?? new object();

        var result = new Dictionary<string, object>();
        foreach (System.Reflection.PropertyInfo fi in item1.GetType().GetProperties().Where(x => x.CanRead))
        {
            var Value = fi.GetValue(item1, null);
            result[fi.Name] = Value;
        }
        foreach (System.Reflection.PropertyInfo fi in item2.GetType().GetProperties().Where(x => x.CanRead))
        {
            var Value = fi.GetValue(item2, null);
            result[fi.Name] = Value;
        }
        return result;
    }
Ruffo answered 9/10, 2014 at 16:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.