【问题标题】:Why is there an ambiguous method call between long, float and decimal methods when using a long as a paramater?当使用 long 作为参数时,为什么在 long、float 和 decimal 方法之间存在模棱两可的方法调用?
【发布时间】:2020-07-21 17:37:01
【问题描述】:

我正在尝试构建一个 NpgsqlSimpleTypeHandler<ulong> 来处理将 ulong 值转换为 long 值,以便它们可以存储在我的 PostgreSQL 数据库中,反之亦然。

当我在使用Convert.ToInt64(value)ulong 转换为long 后尝试调用Npgsql 的Int64handler.ValidtaeAndGetLength 时,尝试传递新转换的long 会给我一个模棱两可的方法调用错误。

private readonly Int64Handler handler;
...
public override int ValidateAndGetLength(ulong value, NpgsqlParameter? parameter)
{
    long val = Convert.ToInt64(value);
    return handler.ValidateAndGetLength(val, parameter);
}

错误信息:

Error   CS0121
The call is ambiguous between the following methods or properties:
'Int64Handler.ValidateAndGetLength(float, NpgsqlParameter?)' and
'Int64Handler.ValidateAndGetLength(decimal, NpgsqlParameter?)'

为什么,即使 Int64Handler.ValidateAndGetLength(long, NpgsqlParameter?) 存在,我最终会出现模棱两可的方法调用错误?

Npgsql Int64Handler documentation

[7/21/2020 14:23 EST] 更新标签以包含 C#-8.0

【问题讨论】:

  • 你试过用float代替long吗?例如...float val = Convert.ToInt64(value);
  • 使用浮点数可以正确编译,但是在我处理雪花(Discord ID)的长值时使用浮点数会产生不利影响吗?
  • 在不相关的注释中,不鼓励编写自己的类型处理程序。尽管这些 API 是公开的,但它们更多地用于“官方”(阅读:维护)Npgsql 插件,并且可能随时中断(计划对 Npgsql 5.0 进行一些改进,可能会这样做)。请注意这一点。

标签: c# npgsql c#-8.0


【解决方案1】:

ValidateAndGetLength(long, NpgsqlParameter?)Int64Handler类中的其他重载的区别在于,第一个方法实际上是重写类NpgsqlSimpleTypeHandler<long>中实现INpgsqlSimpleTypeHandler<long>的方法,而其他方法是实现@中的方法987654326@接口直接。

来自C# 6 draft language specification

例如,方法调用的候选集不包括标记为覆盖的方法(成员查找),如果派生类中的任何方法适用(方法调用),则基类中的方法不是候选方法。

“成员查找”部分对此进行了进一步扩展:

首先,确定一组名为 N 的可访问成员:

如果 T 是类型参数,则该集合是指定为 T 的主要约束或次要约束(类型参数约束)的每个类型中名为 N 的可访问成员集的并集,以及对象中名为 N 的可访问成员。

否则,该集合由 T 中名为 N 的所有可访问(成员访问)成员组成,包括继承成员和对象中名为 N 的可访问成员。如果 T 是构造类型,则通过替换类型参数获得成员集,如构造类型的成员中所述。 包含覆盖修饰符的成员被排除在集合之外。

我设法用示例代码重现了这个问题:

using System;

interface IA<T>
{
    int X(T x);
}

abstract class A<T> : IA<T>
{
    public abstract int X(T x);
}

class B : A<long>, IA<float>, IA<decimal>
{
    public override int X(long x)
    {
        return 8;
    }
    
    public int X(float x)
    {
        return 8;
    }
    
    public int X(decimal x)
    {
        return 8;
    }
}
                    
public class Program
{
    public static void Main()
    {
        var b = new B();
        var a = (A<long>)b;
        Console.WriteLine(a.X(42L));
        // Console.WriteLine(b.X(42L)); // broken
    }
}

您可以使用将处理程序强制转换为 NpgsqlSimpleTypeHandler&lt;long&gt; 并调用该方法的解决方法。

【讨论】:

  • 感谢修复编译错误,谢谢!我的代码仍然被破坏,但现在完全不同的原因!
  • @devNull 我实际上是在深入研究 C# 6 草案语言规范,但你更快,所以我将在我的答案中包含引用。
  • @milleniumbug 我可能应该在我的问题中提到它,但如果你要包含语言规范中的引号,我会查看 C# 8.0 规范,因为我使用的是 C# 8.0
  • @AndrewBounds 啊,到目前为止,我们似乎只有 C# 6 草案规范,较新的似乎是描述与 C# 6 差异的提案。我假设这方面在版本之间没有变化。
  • @milleniumbug 看到它对我有用,不,我认为它没有改变。只是觉得如果有 C# 8 规范就应该使用它,但我自己并没有真正寻找任何东西,所以我显然没有发现缺少规范。
【解决方案2】:

C# 中的一些隐式类型转换规则是从 Java 中借用的,这些规则允许从浮点数隐式转换为双精度数,但反之不行,并且允许从任何整数类型隐式转换为浮点数或双精度数,但不能相反反之亦然。这具有相当奇怪的效果,即导致从整数类型到float 的转换被认为明显优于到double 的转换,尽管精度损失更大。这在调用接受所有整数类型重载的函数的重载时不会成为问题,但在将整数值传递给同时接受 floatdouble 的函数时可能会有点古怪。

然而,C# 的decimal 类型为Java 的类型规则添加了另一个问题。所有整数类型都可以隐式转换为decimal,但decimal 不能隐式转换为任何其他浮点类型或从任何其他浮点类型转换。因此,decimal 并不优于 floatfloat 也不优于 decimal。如果尝试将long 传递给可以接受floatdoubledecimal 但不准备接受long 的函数,则编译器将没有任何依据floatdecimal 比另一个更可取。具有讽刺意味的是,在大多数这种情况下,最好的类型是double,而decimal 类型适合的情况相对较少,但它的存在有助于提醒程序员需要显式转换为double,而不是而不是让编译器静默地将long 转换为float

【讨论】:

  • 我不明白这如何回答所提出的问题。 “如果尝试将 long 传递给可以接受 float、double 和 decimal,但不准备接受 long 的函数” - OP 已明确声明 is 实际上是一个接受long 作为参数类型的重载。为什么编译器不选择完全匹配的重载,而不是抱怨需要转换的两个重载之间的歧义?
  • 这是有道理的,但是为什么编译器无法选择接受 long 值的方法的重载,而是抛出错误?
  • @PeterDuniho:它没有将这样的重载列为现有。我没有看到其余的代码,但值得注意的是,除了相同的泛型类型或其他受限于它们的泛型类型之外,泛型类型永远不会被视为重载。
  • @supercat 出于这个原因,我链接了 Int64Handler 文档。有一个版本的方法接受 long 值,即使它没有出现在错误消息中:public override int ValidateAndGetLength(long value, NpgsqlParameter parameter)
  • @AndrewBounds:第二个参数的类型是NpgsqlParameter还是可以为空的NpgsqlParameter?
猜你喜欢
  • 2013-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-13
  • 1970-01-01
  • 2019-11-28
  • 1970-01-01
相关资源
最近更新 更多