10

If I have a C# class

public class Foo
{
    public int? a { get; set; }
    public int? b { get; set; }
}

And two instances of that class

var foo1 = new Foo() { a = 1 };
var foo2 = new Foo() { b = 1 };

How could I copy the values from both objects to create a new instance of Foo that contained the values from both foo1 and foo2?

In Javascript this would be as simple as

var foo3 = Object.assign({}, foo1, foo2);
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
kavun
  • 3,358
  • 3
  • 25
  • 45
  • 6
    This doesn't really make sense in C# since `foo1` and `foo2` will both always have values for `a` and `b`. So you would just be copying the values of `foo2` into the target. You could make `a` and `b` nullable and write a version which only sets non-null values. – Lee Nov 03 '16 at 13:42
  • I don't think there is anything like that in .NET/C#. You could use reflection to create functionality like that. Also have a look at there, this is basically the same question: http://stackoverflow.com/questions/26156577/is-there-a-jquery-extend-in-c – sknt Nov 03 '16 at 13:45
  • @Lee's suggestion seems a good fit for what you are describing. you could then do an assignment like this `var foo3 = new Foo() {a = foo1.a ?? foo2.a, b = foo1.b ?? foo2.b };` – Theo Nov 03 '16 at 13:49
  • good point @Lee, I'll update my example to include nullable properties to be more realistic. – kavun Nov 03 '16 at 14:19

4 Answers4

9

you could create a method which merges objects via reflection. But beware, this is slow an can generally not used in C#.

Care must be taken to skip "empty" properties. In your case these are value types. In my example implementation, every property is skipped, if its the default value of that type (for int this is 0):

public T CreateFromObjects<T>(params T[] sources)
    where T : new()
{
    var ret = new T();
    MergeObjects(ret, sources);

    return ret;
}

public void MergeObjects<T>(T target, params T[] sources)
{
    Func<PropertyInfo, T, bool> predicate = (p, s) =>
    {
        if (p.GetValue(s).Equals(GetDefault(p.PropertyType)))
        {
            return false;
        }

        return true;
    };

    MergeObjects(target, predicate, sources);
}

public void MergeObjects<T>(T target, Func<PropertyInfo, T, bool> predicate, params T[] sources)
{
    foreach (var propertyInfo in typeof(T).GetProperties().Where(prop => prop.CanRead && prop.CanWrite))
    {
        foreach (var source in sources)
        {
            if (predicate(propertyInfo, source))
            {
                propertyInfo.SetValue(target, propertyInfo.GetValue(source));
            }
        }
    }
}

private static object GetDefault(Type type)
{
    if (type.IsValueType)
    {
        return Activator.CreateInstance(type);
    }
    return null;
}

usage:

var foo3 = CreateFromObjects(foo1, foo2);
Nico
  • 3,542
  • 24
  • 29
  • 10
    there should be some kind of tag for answers that are clever but should absolutely not ever actually be used – Weyland Yutani Nov 03 '16 at 13:59
  • 1
    @WeylandYutani :-) that is true. Maybe I should add a warning. – Nico Nov 03 '16 at 14:01
  • @heinzbeinz other than this being slow, why shouldn't I use some variation of this? – kavun Nov 03 '16 at 14:20
  • @kavun Because normally you don't need that kind of functionality in c#. In javascript you don't have strict type checking and you build object "types" on the fly. I never encountered a need for object merging. What would be your usecase? – Nico Nov 03 '16 at 14:24
  • @heinzbeinz was thinking of using a method like this to implement `PATCH` in ASP.NET Web API. Ex: (1) get object instance from database, (2) get object instance from `PATCH` request, (3) merge the two and save to db. There is this implementation https://github.com/myquay/JsonPatch but that seems like a lot of code for what I'm used to being simple in Javascript. – kavun Nov 03 '16 at 14:29
  • @heinzbeinz your answer is a good starting point for thinking about the problem though, thank you! – kavun Nov 03 '16 at 14:30
  • 2
    @kavun if you want to merge Json there is the excellent [`Json.net`](http://www.newtonsoft.com/json) library available. It supports Json merging via `JObject.Merge` – Nico Nov 03 '16 at 14:33
  • @WeylandYutani why do you recommend not using this functionality. I am not experienced with C# at all, but reflection lib is there for a reason and it should be decently optimized. Suppose you have a big data container object with a lot of members, so should I create a method `assign(otherObj)` where I would assign each member of `otherObj` to `this` manually rather than do simple JS-style `Object.assign(this, otherObj)` and never care about the data it holds, so I wouldn't change this code when deciding to remove or add new members for that data container class? – Veetaha Jan 27 '19 at 13:06
  • Thanks for pointing this out veetaha. I have looked at the answer again and the comment I made two years ago and think i was wrong. I don't have a problem with reflection performance and I don't see anything wrong with how it is used in the answer, if it is for a specific task like perhaps a configuration merge. My guess is I misread the question as asking how to combine fields of two types into a new third type and assumed the answer was doing this using reflection and so was breaking type safety – Weyland Yutani Jan 29 '19 at 17:50
2

There is no direct method for this, But you can fulfil your requirement this way.

var foo3 = new Foo() {a = foo1.a, b = foo2.b };
Vivek Nuna
  • 25,472
  • 25
  • 109
  • 197
2

Vivek's answer is the correct one. Because Object.Assign is part of the larger Inheritance vs Composition discussion. This is not so simple in javascript, where you need Object.Assign, but in C# you can compose a class in your own class by just instantiating it and making it a property. You really do it all the time.

Javascript with it's prototypal inheritance, you're really just linking objects. So inheritance (classical) does not exists there.

Check this video, it's very enlightening: Inheritance vs Composition

Gerben Rampaart
  • 9,853
  • 3
  • 26
  • 32
  • While yes, Vivek's is the "right" one, Nico's is the answer I wanted :) ... nevertheless, Weyland's comment is appropriate --> "answers that are clever but should absolutely not ever actually be used" – kavun Mar 29 '17 at 14:45
-3
using Newtonsoft.Json;
.
.
.

var newFoo = JsonConvert.DeserializeObject<Foo>(JsonConvert.SerializeObject(oldFoo));

regards, Oli

  • 1
    If you wanted to combine `class A` with `class B` you would still have to manually define `class AB` – kavun Jun 04 '18 at 18:28