Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Member selector expression combining two classes

I have two classes (or models rather) having some common properties. For instance:

public class Model1
{
    public int Common1 { get; set; }
    public int Common2 { get; set; }
    public int Prop11 { get; set; }
    public int Prop12 { get; set; }
}

public class Model2
{
    public int Common1 { get; set; }
    public int Common2 { get; set; }
    public int Prop21 { get; set; }
    public int Prop22 { get; set; }
}

I need to write a method that can accept a member selector expression where the member to select can come from either of the two models. This is what I mean:

public List<T> GetAnon<T>(Expression<Func<??, T>> selector) // <-- intriguing part
{
    // codes to extract property names from selector and create the return object of type T
}

// And this is an example usage of the method
// At this point, I don't know which model Common1, Prop11 or Prop21 belongs to
var anonObj = GetAnon(m => new { m.Common1, m.Prop11, m.Prop21 });
// anonObj = { Common1 = VALUE_1, Prop11 = VALUE_2, Prop21 = VALUE_3 }

Please note that the selector passed selects both from Model1 and also from Model2.

My current solution: Create a model with the common properties, say Model, have Model1 and Model2 inherit Model with the rest of the properties in the individual models. Then make another model, say TempModel, that inherits Model, and add the non-common properties to it. Like so:

public class Model
{
    public int Common1 { get; set; }
    public int Common2 { get; set; }
}

public class Model1 : Model
{
    public int Prop11 { get; set; }
    public int Prop12 { get; set; }
}

public class Model2
{
    public int Prop21 { get; set; }
    public int Prop22 { get; set; }
}

public class TempModel : Model
{
    public int Prop11 { get; set; }
    public int Prop12 { get; set; }
    public int Prop21 { get; set; }
    public int Prop22 { get; set; }
}

// Finally, first type parameter can be TempModel
public List<T> GetAnon<T>(Expression<Func<TempModel, T>> selector)
{
    // codes to extract property names from selector and create the return object of type T
}

The problem with this approach is that if Model1 and Model2 change, which they are prone to, I will have to remember to make the changes to TempModel as well, which is something I would like to avoid. Also, I would like to avoid passing the names of the properties as strings, so that I do not lose intellisense.

How can I achieve this? Obviously, multiple-inheritance is not possible in C#, otherwise my problem would have become trivial.

Edit:

I have edited my question to reflect my actual need (though I am not sure if that really helps). I actually need to create and return an object of anonymous type T inside the method GetAnon(). To create the object I need the names of the properties so that I can perform the retrieval (using SQL). I don't want to lose intellisence by passing names as strings. Also, at the point of calling the method, I do not know which property comes from which model, all I know are the property names.

like image 477
Sнаđошƒаӽ Avatar asked Feb 25 '18 15:02

Sнаđошƒаӽ


2 Answers

I think you may be over-complicating the use of the selector function. I'm assuming you want to use a lambda there because you don't want to have to worry about changing the property name in a string if you change it in your model. That is exactly what the nameof function was designed for.

public List<string> GetMemberNames<T1, T2>(IEnumerable<string> propNames)
{
    var commonProps = (from pn in propNames
                       where
                       typeof(T1).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                 .Any(p => p.Name == pn) ||
                       typeof(T2).GetProperties(BindingFlags.Instance | BindingFlags.Public)
                                 .Any(p => p.Name == pn)
                       select pn).ToList();

    return commonProps;
}

Usage:

var cp = GetMemberNames<Model1, Model2>(new[] 
                        { nameof(Model1.Common1),   
                          nameof(Model2.Common2), 
                          nameof(Model1.Prop11), 
                          nameof(Model2.Prop21),
                          "SomeUnknownProperty", 
                        });

Notice now you don't need to have an instance of either model to obtain the list of property names.

Result:

enter image description here

like image 156
Ron Beyer Avatar answered Nov 02 '22 12:11

Ron Beyer


I think the all around easiest way would be to just use:

public List<T> GetAnon<T>(Expression<Func<Model1, Model2, T>> selector) {

}

It might be a little bit inconvenient for the caller, because he has to remember (or figure it out by trial and error) which model has property he needs, but I think inconvenience is minor and benefit is big enough (no need for additional classes, which as you said are error-prone to maintain).

Usage will be:

var propNames = GetAnon((m1, m2) => new { m1.Common1, m1.Prop11, m2.Prop21 });
like image 30
Evk Avatar answered Nov 02 '22 11:11

Evk