2

This is my method. Note that I am returning the equivalent nullable type for the generic parameter R:

    public static Nullable<R> GetValue<T, R>(this T a, Expression<Func<T, R>> expression)
        where T : Attribute
        where R : struct
    {
        if (a == null)
            return null;

        PropertyInfo p = GetProperty(expression);
        if (p == null)
            return null;

        return (R)p.GetValue(a, null);
    }

I can use it in a call to get the value of an attribute like this:

//I don't throw exceptions for invalid or missing calls 
//because I want to chain the calls together:
int maximumLength4 = instance.GetProperty(x => x.ToString())
                            .GetAttribute<StringLengthAttribute>()
                            .GetValue(x => x.MaximumLength)
                            .GetValueOrDefault(50);

I'd like to use the same generic method with strings:

//I'd like to use the GetValue generic method with strings as well as integers 
string erroMessage = instance.GetProperty(x => x.ToString())
                            .GetAttribute<StringLengthAttribute>()
                            .GetValue(x => x.ErrorMessage);

but it won't compile:

The type 'R' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable'

Cannot implicitly convert type 'string?' to 'string'

Is there any trick I can use to get the same method signature here and yet get generics to infer the return type as one that can be null?

This is some test code to show that it works for integer values:

//[StringLength(256)]
//public string Name { get; set; }
PropertyInfo info = ReflectionAPI.GetProperty<Organisation, String>(x => x.Name);//not null
StringLengthAttribute attr = info.GetAttribute<StringLengthAttribute>();//not null
int? maximumLength = attr.GetValue(x => x.MaximumLength);//256
int? minimumLength = attr.GetValue(x => x.MinimumLength);//0

PropertyInfo info2 = ReflectionAPI.GetProperty<Organisation, int>(x => x.ID);//not null
StringLengthAttribute attr2 = info2.GetAttribute<StringLengthAttribute>();//null because ID doesn't have the attribute
int? maximumLength2 = attr2.GetValue(x => x.MaximumLength);//null
int? minimumLength2 = attr2.GetValue(x => x.MinimumLength);//null

//I can use the GetProperty extension method on an instance
Organisation instance = (Organisation)null;
PropertyInfo info3 = instance.GetProperty(x => x.ToString());//null because its a method call not a property
StringLengthAttribute attr3 = info3.GetAttribute<StringLengthAttribute>();//null
int? maximumLength3 = attr3.GetValue(x => x.MaximumLength);//null
int? minimumLength3 = attr3.GetValue(x => x.MinimumLength);//null

And this is the rest of my ReflectionAPI:

public static class ReflectionAPI
{

    public static Nullable<R> GetValue<T, R>(this T a, Expression<Func<T, R>> expression)
        where T : Attribute
    {
        if (a == null)
            return null;

        PropertyInfo p = GetProperty(expression);
        if (p == null)
            return null;

        return (R)p.GetValue(a, null);
    }

    public static T GetAttribute<T>(this PropertyInfo p) where T : Attribute
    {
        if (p == null)
            return null;

        return p.GetCustomAttributes(false).OfType<T>().LastOrDefault();
    }

    public static PropertyInfo GetProperty<T, R>(Expression<Func<T, R>> expression)
    {
        if (expression == null)
            return null;

        MemberExpression memberExpression = expression.Body as MemberExpression;
        if (memberExpression == null)
            return null;

        return memberExpression.Member as PropertyInfo;
    }
}
Colin
  • 22,328
  • 17
  • 103
  • 197

1 Answers1

5

No, there's no individual signature that can do this - there's no way of saying "the nullable type of R, which is either R itself for a reference type, or Nullable<R> for a non-nullable value type".

You can have different methods, each with a different constraint on R - but then you either have to provide different names, or use horrible hacks to effectively overload by type parameter constraints.

I suspect you should basically have two different method names here, for simplicity. So signatures of:

public static Nullable<R> GetValue<T, R>(this T a, Expression<Func<T, R>> expression)
    where T : Attribute
    where R : struct

public static R GetReference<T, R>(this T a, Expression<Func<T, R>> expression)
    where T : Attribute
    where R : class

Two asides:

  • Conventionally type parameters start with T, e.g. TInput and TResult
  • Why are you using expression trees at all for GetValue? Why not just take a Func<T, R> and execute it?

As an example of the second bullet point:

public static Nullable<R> GetValue<T, R>(this T a, Func<T, R> func)
    where T : Attribute
    where R : struct
{
    if (a == null)
        return null;

    return func(a);
}
RJ Cuthbertson
  • 1,458
  • 1
  • 20
  • 36
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I thought that might be the case, Jon. I'm using an expression tree bcause that appears to be the most common way that people go about getting a PropertyInfo using lambdas rather than magic strings, not because I really understand it. If I promise to go back and read that chapter in your book again will you show me an example of what you mean now? ;-) – Colin Oct 24 '13 at 16:32
  • 1
    Note that if you have these two methods you can accept any object that a method without either constraint could *except for a nullable struct*. You need to add a third overload where `T` is a nullable struct to really get everything. – Servy Oct 24 '13 at 17:45
  • @Servy I was thinking of changing my signatures to `Nullable GetValueOrDefault(this T a, Func func, Nullable default)` and `R GetValueOrDefault(this T a, Func func, string default)` I was feeling pleased with that because it makes the developer handle the null problem that I am delaying by chaining. But your point means that attributes with nullable parameters aren't covered. But maybe it's a small edge case I don't have to worry about because of this:http://stackoverflow.com/questions/1416126/why-is-a-nullablet-not-a-valid-custom-attribute-parameter-when-t-is – Colin Oct 24 '13 at 21:24
  • @Colin If you have no `class` or `struct` constraint then it's fine. It's just an odd case that nullable technically is not a `class` or a `struct` as per the generic constraints. It's a struct, but it still "fails" that check. You could still close the gap with a 3rd overload, if it's important to cover it though. – Servy Oct 24 '13 at 21:31
  • @Servy not sure there's a good overload though because the default for both an `int` and an `int?` needs to be an `int?` too...I think the easiest is just to remove the constraint. Many thanks to you both I've learned a lot today – Colin Oct 24 '13 at 21:46