3

Sometimes, when handling data transfer objects (for instance retrieved from the database or a csv file), it's nice to write some helper functions to move the data.

For instance:

class MyDto
{
    public string Name { get; set; }
}

class MyBusinessObject
{
    public string Name { get; set;}
}

I'd like to write something like:

MyDto source;
MyBusinessObject target;

var hasChanged = target.Set(source, source => source.Name, target => target.Name); // lamdba expressions, or whatever it takes to make it work

with the extension method:

public static bool Set<TS, TT, TValue>(this TS source, IGetProperty<TS, TValue> sourceGetProperty, IGetOrSetProperty<TT, TValue> targetGetOrSetProperty)
{
    var sourceValue = sourceGetProperty.Invoke(source);
    var actualValue = targetGetOrSetProperty.Invoke(target);
    if(sourceValue != actualValue)
    {
        targetGetOrSetPropery.Invoke(target, sourceValue);
        return true;
    }
    return false;
}

I made up the IGetProperty and IGetOrSetProperty. Is it possible to implement them some way without using reflection (so that it's compile-time checked)?

Or is there an elegant way to handle this kind of situation?

EDIT: the example was misleading because the goal wasn't to use an Automapper, but to represent somehow properties as objects. I realize that it's pretty close in fact to the idea of using properties as "ref" for instance, so it's more a language-related question that has always been answered here: Passing properties by reference in C#

Community
  • 1
  • 1
  • 7
    Instead of reinventing the wheel you could use something like [AutoMapper](http://automapper.org/) – Nasreddine May 31 '16 at 15:22
  • Rather than the fictional `IGetProperty`, you're looking for `Expression>`, and then you're almost there. (But do look at AutoMapper first.) – Jeroen Mostert May 31 '16 at 15:24
  • Maybe my example was not well chosen, but it's not pure mapping I'm trying to do, but sequential, sometimes conditional, operations on properties (hence my attempt to represent a property accessor as an object). By using Automapper, I'd be forced to duplicate the test "does the value change?" in the lambdas of the BeforeMap for instance, which is not elegant. The Expression on the other hand doesn't allow to represent a get/set property, and requires compilation at runtime (unless I'm mistaken). – Vincent Ripoll May 31 '16 at 15:45
  • @VincentRipoll you cannot do this kind of things without using reflection unless both of these classes has some common base class or interface. For your problem use of `Automapper` would be the great solution indeed. If you've another problem then you can always edit this question or write a new one. – Fabjan May 31 '16 at 15:48

2 Answers2

3

It's not exactly possible without reflection, but the expression lambda gives you compile time checking:

public static bool Set<TTarget, TValue>(
    this TTarget target, 
    Expression<Func<TTarget, TValue>> targetProperty, 
    TValue sourceValue)
{
    var actualValue = targetProperty.Compile().Invoke(target);

    if (actualValue.Equals(sourceValue))
    {
        return false;
    }

    var property = (PropertyInfo)((MemberExpression)targetProperty.Body).Member;
    property.SetValue(target, sourceValue);
    return true;
}

Usage looks like so:

var hasChanged = target.Set(t => t.Name, source.Name);

Working example: https://dotnetfiddle.net/CJVxIS

Why you should not do this:

  • targetProperty.Compile() is slow,
  • Automapper does such mappings for you.
Good Night Nerd Pride
  • 8,245
  • 4
  • 49
  • 65
0

You can consider serialising/deserialising. It may be less efficient (but then again, rudimentary implementation of reflection is expensive too) but it will be syntactically more readable and elegant. The other benefit is, Json.Net is very flexible so you can customise the copy behaviours (e.g map Name property to a property of another name)

class MyDto
{
    public string Name { get; set; }
    public string Other { get; set; }
    public string Remap { get; set; }
}

class MyBusinessObject
{
    [JsonIgnore]
    public string Other { get; set; }

    public string Name { get; set; }

    [JsonProperty(PropertyName = "Remap")]
    public string RemmapedField { get; set; }
}

public T DeepCopy<T>(object o)
{
    string json=JsonConvert.SerializeObject(o);
    T newO=JsonConvert.DeserializeObject<T>(json);
    return newO;
}

Usage

MyDto source = new MyDto() { Name = "JP", Other = "Something",Remap="R" };
var target = DeepCopy<MyBusinessObject>(source);

Result:

Name: "JP"
Other: null
RemmapedField: "R"

Jeff Pang
  • 161
  • 1
  • 6