【问题标题】:Can I create a generic method that takes a value type or a reference type but always returns a nullable type我可以创建一个采用值类型或引用类型但始终返回可为空类型的泛型方法吗
【发布时间】:2013-10-24 16:13:12
【问题描述】:

这是我的方法。请注意,我正在返回泛型参数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 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 GetValue generic method with strings as well as integers 
string erroMessage = instance.GetProperty(x => x.ToString())
                            .GetAttribute<StringLengthAttribute>()
                            .GetValue(x => x.ErrorMessage);

但它不会编译:

类型“R”必须是不可为空的值类型才能将其用作 泛型类型或方法“System.Nullable”中的参数“T”

不能隐式转换类型“字符串?”到“字符串”

我可以使用什么技巧在这里获得相同的方法签名,同时让泛型将返回类型推断为可以为 null 的类型?

这是一些测试代码,表明它适用于整数值:

//[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

这是我的 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;
    }
}

【问题讨论】:

    标签: c# generics reflection data-annotations


    【解决方案1】:

    不,没有单独的签名可以做到这一点 - 没有办法说“R 的可空类型,它要么是 R 本身作为引用类型,要么是 Nullable&lt;R&gt; 用于不可空值类型”。

    您可以有不同的方法,每个方法对R 有不同的约束 - 但是您要么必须提供不同的名称,要么使用 horrible hacks有效地通过类型参数约束来重载。

    为了简单起见,我怀疑您在这里基本上应该有两个不同的方法名称。所以签名:

    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
    

    两旁:

    • 通常类型参数以T 开头,例如TInputTResult
    • 你为什么要为GetValue 使用表达式树?为什么不直接使用Func&lt;T, R&gt; 并执行它呢?

    以第二个要点为例:

    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);
    }
    

    【讨论】:

    • 我想可能是这样,乔恩。我正在使用表达式树,因为这似乎是人们使用 lambdas 而不是魔术字符串获取 PropertyInfo 的最常见方式,而不是因为我真的理解它。如果我答应再读一遍你书中的那一章,你能给我举个例子来说明你现在的意思吗? ;-)
    • 请注意,如果您有这两种方法,您可以接受没有任何约束的方法可以除了可为空的结构之外的任何对象。您需要添加第三个重载,其中 T 是一个可以为空的结构来真正获取所有内容。
    • @Servy 我正在考虑将我的签名更改为Nullable&lt;R&gt; GetValueOrDefault&lt;T, R&gt;(this T a, Func&lt;T, R&gt; func, Nullable&lt;R&gt; default)R GetValueOrDefault&lt;T, R&gt;(this T a, Func&lt;T, R&gt; func, string default) 我对此感到很满意,因为它使开发人员能够处理我通过链接延迟的空问题。但是您的观点意味着不包括具有可为空参数的属性。但也许这是一个我不必担心的小边缘情况:stackoverflow.com/questions/1416126/…
    • @Colin 如果您没有classstruct 约束,那很好。根据通用约束,从技术上讲,可为空的不是classstruct,这只是一个奇怪的情况。这是一个结构,但它仍然“失败”了检查。你仍然可以通过第三次重载来缩小差距,如果重要的是要弥补它。
    • @Servy 不确定是否存在良好的重载,因为 intint? 的默认值也必须是 int?...我认为最简单的方法就是删除约束。非常感谢你们,我今天学到了很多
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-01
    • 2019-07-02
    • 2018-06-12
    • 1970-01-01
    • 2019-11-04
    • 1970-01-01
    相关资源
    最近更新 更多