泛型、可空类型、类型推断和函数签名冲突

5

I've this code :

public async static Task<T?> RequestValue1<T>(Command requestCommand)
                    where T : struct 
{
    // Whatever
}

public async static Task<T> RequestValue2<T>(Command requestCommand)
                    where T : class 
{
    // Whatever
}

我希望我的两个方法有相同的名称。这是否可能?

我的问题:

  • 因为返回类型不同,我必须写两个不同的方法, 如果请求失败则返回null,如果请求成功则返回值,其中如果T是值类型,则返回Nullable<T>,如果T是引用类型,则返回T的实例。
  • async 不允许使用 ref/out,因此没有类型为 T 的方法参数,无法推断出 T,我的两个方法无法具有相同的名称(签名冲突)。因为泛型约束对于解决签名冲突的情况不适用,如果没有推断出T

目前这段代码可以工作,但我不喜欢在“RequestValue1”和“RequestValue2”之间进行奇怪的函数调用。


2
一个方法的返回类型在方法重载的目的上不是方法签名的一部分。但是如果增加另一个输入参数来区分呢? - J. Steen
5
如果可能的话,你可以为它们取一个更好的名字,至少不要用数字1和2,例如RequestClassRequestStruct。请注意,翻译后的内容应与原文意思相同且更易于理解,不得添加说明或其他非翻译相关的内容。 - Servy
你是否可以不总是调用类型为 intdouble 等的 RequestValue2 变体,而是使用类型参数 int?double? 等? - Rawling
编辑:恢复有关删除 :class 的注释,使用 : new() 或甚至什么都不用代替。 - Rawling
@Rawling :) new() 也是一个不错的想法,但它排除了 string :( - Nicolas Voron
显示剩余7条评论
3个回答

4
您可以创建自己的“Option”类型,并使用该类型来指示是否返回值:
您可以创建自己的“Option”类型,并使用它来表示是否返回值:
public async static Task<Option<T>> RequestValue<T>(Command requestCommand) {
  ...
}

更新: Option<T> 类型的目的是取代 nullNullable<T>,但如果您仍然想使用它们,您可以使用这些扩展方法来弥合差距:

public static class OptionExtensions {
  public static T? GetNullableValue<T>(this Option<T> option) where T : struct {
    return option.HasValue ? (T?)option.Value : null;
  }
  public static T GetValueOrNull<T>(this Option<T> option) where T : class {
    return option.HasValue ? option.Value : null;
  }
}

好主意。可惜不能这样一行代码实现(等待 Option<T> 然后获取 Option<T>.Value),因为这个调用必须重复多次。 - Nicolas Voron
@NicolasVoron:只有在 Option<T>.HasValue 的情况下,您才能获得 Option<T>.Value - Jordão
@NicolasVoron:另外,为什么多次重复调用会有问题?我不太明白。 - Jordão
嗯...试图比较int? i = await RequestValue<int>(Command requestCommand)Option<int> oi = await RequestValue<int>(Command requestCommand); int? i = (oi != null) ? oi.Value : null。这不是真正的问题,只是一条备注。;) - Nicolas Voron
啊,我明白了!如果你能尽可能地使用Option<T>类型而不是使用null,那会更好。这也意味着你不需要使用Nullable<T>。实际上,你正在用一个更通用的概念来替换Nullable<T> - Jordão
1
我认为这绝对是正确的方法(扩展方法通过返回被包装丢失的灵活性)。非常感谢Jordão! :) - Nicolas Voron

1
您可以放弃约束,并让调用者将Nullable<T>类型传递给您的方法(即调用RequestValue<int?>(cmd)而不是RequestValue<int>(cmd))。您可以在运行时确保可空性,如下所示:

public async static Task<T> RequestValue<T>(object arg) {
    var t = typeof (T);
    if (!t.IsClass && (!t.IsGenericType || t.GetGenericTypeDefinition() != typeof(Nullable<>))) {
        throw new ArgumentException("T");
    }
    // Whatever
}

1
如果您始终传递可空类型,则可以在null的位置使用default(T)。如果有人想使用非可空类型...这并不是世界末日。 - Servy
@Servy 你是正确的,使用 default(T) 比显式地设置 null 更好。这种解决方案的唯一缺点是它将类型检查从编译时移动到运行时。 - Sergey Kalinichenko
@NicolasVoron 如果您按照上述检查方式进行操作,则传递非可空的T将触发异常,确保“世界末日”提前到来。一旦您通过该检查点,就可以自由设置default(T),它始终是null - Sergey Kalinichenko
@Servy 我同意你的观点。但是不要低估“非常糟糕的编码人员,他们什么都不懂,然后问我问题”的力量 ;) - Nicolas Voron
1
@NicolasVoron 如果你想让它傻瓜化,那就只使用两个方法名称;这很简单。如果你假设用户特别愚蠢或恶意,那么你不应该寻找其他解决方案。 - Servy
显示剩余4条评论

0

一个方法可以有如下的签名:

    static void test1<T>(Nullable<T> param) where T:struct;
    static void test1<T>(T param) where T:class;

没有冲突。试图传递一个非空结构类型将失败,但编译器在参数为可空类型或类类型时选择重载不会有问题。

您的情况略有不同,因为您并没有传递特定类型的参数;您只是尝试传递类型本身。没有实际类型的参数,编译器将无法确定Nullable<T>比另一个方法更适合。我的倾向是提供一种使用除结果的空值以外的其他方式来指示成功或失败的方法。正常的Try模式如下:

static bool RequestValue1<T>(Command requestCommand, out Task<T> Result);

我不是特别喜欢那种模式,因为 Result 无法协变或参与类型推断。另一种选择是:

static Task<T> RequestValue1<T>(Command requestCommand, out bool Success);

这样的表格在协方差方面没有问题。另一种替代形式可能是:

static Task<T> RequestValue1<T>(Command requestCommand, out ResultStatus Status);

其中ResultStatus将是一个类型,它具有Succeeded方法,在成功的情况下返回True,但在失败的情况下可能会有其他成员来解释出了什么问题。如果它是一个不可变的抽象类型,为当事情顺利进行时提供单例Success实例,它可以在未来扩展以在事情不顺利时提供任意级别的详细信息而不会在事情顺利进行时造成任何GC压力。

不幸的是,即使out参数类型与T无关的形式也无法在某些上下文中使用。为了允许这样做,可以定义一个结构类型CommandResult<T>,它将T与成功指示器结合起来,这在概念上类似于Nullable<T>,但其参数类型不会受到烦人的struct约束。成功指示器可以是bool,也可以是如上所述的状态指示器。


这对我来说将是完美的模式。但正如我在问题中所说,“async”不允许使用ref/out参数。 - Nicolas Voron
但是我的方法必须是异步的 :( - Nicolas Voron
@NicolasVoron:如果你定义了自己的结构类型CommandResult<T>,有什么问题吗? - supercat
是的,我认为这是最好的选择。请参考Jordão的回答 :) - Nicolas Voron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接