【问题标题】:A generic function to accept both reference types and nullable types to accommodate the "as" keyword possible?接受引用类型和可为空类型以适应“as”关键字的通用函数?
【发布时间】:2015-10-18 06:52:00
【问题描述】:

这纯粹是好奇/挑战,根本没有实际意义。 所以我不是在寻找完成工作的替代解决方案。

从这个问题Most efficient way to check for DBNull and then assign to a variable? 我发现this answer 看起来像:

oSomeObject.IntMemeber = oRow["Value"] as int? ?? iDefault;
oSomeObject.StringMember = oRow["Name"] as string ?? sDefault;

我可以将上述表达式移动到一个通用函数(或两个,或更多),以便它同时接受int?string,我可以这样称呼它:

oSomeObject.IntMemeber = oRow.Read<int?>("Value", 0); //iDefault is now 0
//or
oSomeObject.IntMemeber = oRow.Read<int>("Value"); //iDefault is now default(int)

//and 
oSomeObject.StringMember = oRow.Read<string>("Name"); //sDefault is now default(string)

要求:

1) 在DBNulls 的情况下,我需要一个选项来指定默认值。如果我没有指定默认值,我还需要一个返回default(T) 的选项。所以这行不通:

public static T Read<T>(this IDataRecord dr, string field, T defaultValue) where T : class
{
    return dr[field] as T ?? defaultValue;
}

public static T? Read<T>(this IDataRecord dr, string field, T? defaultValue) where T : struct
{
    return dr[field] as T? ?? defaultValue;
}

因为我不能打电话给oSomeObject.StringMemeber = oRow.Read&lt;string&gt;("Name")

它不需要是可选参数,它甚至可以是一个重载:

public static T Read<T>(this IDataRecord dr, string field) where T : class
{
    return dr[field] as T ?? default(T);
}

public static T? Read<T>(this IDataRecord dr, string field) where T : struct
{
    return dr[field] as T? ?? default(T?);
}

public static T Read<T>(this IDataRecord dr, string field, T defaultValue) where T : class
{
    return dr[field] as T ?? defaultValue;
}

public static T? Read<T>(this IDataRecord dr, string field, T? defaultValue) where T : struct
{
    return dr[field] as T? ?? defaultValue;
}

这不会编译,因为方法 1 和 2 具有相同的签名。

2) 泛型函数(在重载的情况下)应该具有相同的名称。

3) as 关键字必须使用检查和强制转换类型。正如我之前所说,我并不是真的在寻找从IDataRecord 读取或提高性能或其他东西的解决方案。


有类似的问题

  1. C# generic class using reference types and nullable value types

  2. Is creating a C# generic method that accepts (nullable) value type and reference type possible?

  3. Can a Generic Method handle both Reference and Nullable Value types?

但这对于as 关键字非常具体。所以那里的答案不适用。

附录:我知道不会有一个单一的解决方案。我会接受哪个是最优雅的选择。

【问题讨论】:

  • “通用”意味着相同的 IL 可用于匹配约束的 所有 类型。问题是引用类型和可为空的值类型对于同一个运算符需要不同的 IL (as)。所以你需要不同的方法。并且同名方法的参数列表必须不同。因此,除非您正在寻找一种解决方案,例如在运行时为每种类型编译一些 IL,否则我会说它不会比您已经拥有的更优雅。
  • 嗯,但遗憾的是我还没有解决方案。
  • 为您的方法使用不同的名称?
  • 是的,这总是可能的。似乎这是最优雅的解决方案:)

标签: c# generics nullable overload-resolution as-keyword


【解决方案1】:

您链接到的答案似乎很清楚地表明,没有一种好方法可以让泛型方法以您想要的干净方式处理引用类型和值类型。如图所示,答案似乎是给它们起不同的名字。

但是,您可以通过使用 defaultValue 参数的默认值将代码减少到只有两个方法而不是四个方法:

public static T Read<T>(this IDataRecord dr, string field, 
                        T defaultValue = null) where T : class
{
    return dr[field] as T ?? defaultValue;
}

public static T? ReadNullable<T>(this IDataRecord dr, string field, 
                                 T? defaultValue = null) where T : struct
{
    return dr[field] as T? ?? defaultValue;
}

这样,当您希望结果为 null 时,您可以使用 dr.ReadNullable(field),而当您想要指定默认值时,您可以使用 dr.ReadNullable(field, someValue)

如下所述,在您的原始代码中使用default(T)default(T?) 不是必需的,因为它们始终为空。

【讨论】:

  • default(T?)null。你的意思是default(T)
  • default(T?) 不是更好吗?
  • x ?? nullx 相同——如果在这种情况下返回null,为什么还要检查x 是否为null
  • @dtb 我尽可能地接近 nawfal 的原始代码来回答手头的问题,但它确实让我觉得有点奇怪。 Nawfal,只是在转换失败时返回空值吗?
  • 我明白了。普遍的共识似乎是 .NET 不能仅基于泛型类型约束来解决重载。
【解决方案2】:

想法是使用is 而不是as。 以下方法可以解决问题:

    public static T Read<T>(this IDictionary<string, object> dr, string field, T defaultValue)
    {
        var v = dr[field];
        return v is T ? (T)v : defaultValue;
    }

    public static T Read<T>(this IDictionary<string, object> dr, string field)
    {
        var v = dr[field];
        return v is T ? (T)v : default(T);
    }

用法:

    Dictionary<string, object> d = new Dictionary<string, object>();

    d["s"] = "string";
    d["i"] = 5;
    d["db.null"] = DBNull.Value;

    Console.WriteLine(d.Read("i", 7));                        // 5
    Console.WriteLine(d.Read("s", "default string"));         // string
    Console.WriteLine(d.Read("db.null", "default string"));   // default string
    Console.WriteLine(d.Read("db.null", -1));                 // -1
    Console.WriteLine(d.Read<int>("i"));                      // 5
    Console.WriteLine(d.Read<string>("s"));                   // string
    Console.WriteLine(d.Read<int>("db.null"));                // 0
    Console.WriteLine(d.Read<string>("db.null") ?? "null");   // null

我使用Dictionary 进行简单示例,IDataRecord 的行为方式相同。

【讨论】:

  • 这不是我的问题。
  • @nawfal 为什么这不是你的问题?不符合哪项要求?
  • 第三个。您不仅要从读者那里阅读两次,而且还要检查两次演员表。 as 避免了这种情况。我知道这些并不昂贵,但这不是我的问题。即使在我复制测试样本的链接中,也有很多使用is 的解决方案,包括我自己的一个。如果您真的是优化的粉丝,您应该看到该链接并避免使用您发布的链接:)
  • @nawfal 规范在这一点上很明确; as(在非动态情况下)被定义为 is 的语法糖。查看这篇文章blogs.msdn.com/b/ericlippert/archive/2010/09/16/…
  • @nawfal 我更新了代码以帮助您了解这里不需要访问阅读器两次 :)
【解决方案3】:

似乎没有一种方法。 JLRishe 的回答很好。它依赖于重命名重载。这是一些不重命名函数,但在调用方便性上的妥协(即少一个可选参数)

static T Read<T>(this object obj, T defaultValue) where T : class
{
    return obj as T ?? defaultValue;
}

static T? Read<T>(this object obj, T? defaultValue) where T : struct
{
    return obj as T? ?? defaultValue;
}

public static T Read<T>(this IDataRecord dr, string field, 
                        T defaultValue) where T : class //no optional parameter here :(
{
    return dr[index].Read<T>(defaultValue);
}

public static T? Read<T>(this IDataRecord dr, string field, T? defaultValue = null) 
                        where T : struct
{
    return dr[index].Read<T>(defaultValue);
}

然后调用,例如:

Session s = new Session();
s.Id = r.Read("", (byte[])null);
s.UserId = (int)r.Read<int>("");
s.LoginTime = (DateTime)r.Read<DateTime>("");
s.LogoutTime = r.Read("", default(DateTime?));
s.MachineFingerprint = r.Read("", (string)null);

正如你所见,引用类型需要指定一个默认值。没那么优雅..

【讨论】:

    猜你喜欢
    • 2019-06-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-30
    • 2020-07-28
    • 1970-01-01
    • 1970-01-01
    • 2022-01-12
    相关资源
    最近更新 更多