tostringtheory
tostringtheory

Reputation: 877

Use type of this Type argument in extension method for TIn of a Func in the same method signature

I was pretty sure that this was possible, but for some reason, I can't seem to figure this out... I am trying to make an extension method off of Type, that will take in a Func to a property from that type, and extract a DefaultValue from the DefaultValueAttribute.

I can get it working, but only if I specify the type arguments for the GetDefaultValue function call. Here is my code as I have it currently:

Person entity:

public class Person
{
    public string FirstName { get; set; }

    [DefaultValue("1234")]
    public string DOB { get; set; }
}

Calls to the method:

//Messing around in LinqPad - .Dump() is LinqPad method

//Works
//typeof(Person).GetDefaultValue<Person, string>(x=>x.DOB).Dump();

//Trying to get to work
//Can't figure out how to get it to infer that TIn is of the Type type.....
typeof(Person).GetDefaultValue(x=> x.DOB).Dump();

This is where the method calls are going to... I am just trying to figure out the means right now before I incorporate into my actual program... Error checking will come into play once I've figured out either how to do it, or to give up b/c it can't be done...

public static class Extensions
{
    //    Works
    //    public static TProperty GetDefaultValue<TModel, TProperty>(this Type type, Expression<Func<TModel, TProperty>> exp)
    //    {
    //        var property = typeof(TModel).GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
    //        var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
    //        return (TProperty)defaultValue.Value;
    //    }

    //trying to get to work
    //I know that I can't do the following, but it is basically what I am trying to do...  I think!
    public static TProperty GetDefaultValue<TProperty>(this Type type, Expression<Func<typeof(type), TProperty>> exp) 
    {
        var property = type.GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
        var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
        return (TProperty)defaultValue.Value;
    }

    //GetFullPropertyName c/o: http://stackoverflow.com/users/105570/
    //ref: http://stackoverflow.com/questions/2789504/
    public static string GetFullPropertyName<TModel, TProperty>(Expression<Func<TModel, TProperty>> exp)
    {
        MemberExpression memberExp;

        if (!TryFindMemberExpression(exp.Body, out memberExp))
               return String.Empty;

        var memberNames = new Stack<string>();
        do
            memberNames.Push(memberExp.Member.Name);
        while (TryFindMemberExpression(memberExp.Expression, out memberExp));

        return String.Join(".", memberNames.ToArray());
    }

    private static bool TryFindMemberExpression(Expression exp, out MemberExpression memberExp)
    {
        memberExp = exp as MemberExpression;
        if (memberExp != null)
            return true;

        if (IsConversion(exp) && exp is UnaryExpression)
        {
            memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
            if (memberExp != null)
                return true;
        }    

        return false;
    }

    private static bool IsConversion(Expression exp)
    {
        return exp.NodeType == ExpressionType.Convert || exp.NodeType == ExpressionType.ConvertChecked;
    }
}

Am I crazy, or is this actually possible? Thank you in advance for your help!

Upvotes: 3

Views: 635

Answers (1)

Chris Shain
Chris Shain

Reputation: 51339

That's not how it works- typeof(Person) doesn't have a property called DOB, but a class whose type is Person does. What you want is to make your extension method generic:

public static TValue GetDefaultValue<TClass, TValue>(this TClass val, Expression<Func<TClass, TValue>> getter) {
    var type = typeof(TClass);
    var property = type.GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
    var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
    return (TProperty)defaultValue.Value;
}

and call it something like:

Person somePerson = GetMeAPerson();
somePerson.GetDefaultValue(p=>p.DOB);

Note that I have not tested the above, but I've seen similar code in the past that worked. That said, I suspect that what you were originally trying to do isn't very appealing this way because you need to make a Person instance first.

Another, possibly more appealing approach is to not make it an extension method at all:

public static TValue GetDefaultValue<TClass, TValue>(Expression<Func<TClass, TValue>> getter) {
    var type = typeof(TClass);
    var property = type.GetProperties().ToList().Single(p => p.Name == GetFullPropertyName(exp));
    var defaultValue = (DefaultValueAttribute)property.GetCustomAttributes(typeof(DefaultValueAttribute), false).FirstOrDefault();
    return (TProperty)defaultValue.Value;
}

Then you can call it without an instance, but the inference isn't as nice):

var defaultValue = GetDefaultValue<Person, DateTime>(p => p.DOB);

Upvotes: 2

Related Questions