【问题标题】:32 bit implicit conversions fail generic overload resolution32 位隐式转换无法通过通用重载解决方案
【发布时间】:2019-10-16 02:34:54
【问题描述】:

我正在尝试自定义整数类型,遇到了一个有趣的问题,涉及泛型、隐式转换和 32 位整数。

以下是如何重现问题的精简示例。如果我有 两个int 转换为 MyInt 的隐式方法,反之亦然,我会收到一个编译错误,看起来 C# 无法解析要使用的泛型类型。它只发生intuint。所有其他整数类型都可以正常工作:sbyte,byte,short,ushort,long,ulong

如果我删除其中一种隐式转换方法,它也可以正常工作。与循环隐式转换有关吗?

using Xunit;

public class MyInt
{
    public int Value;

    //If I remove either one of the implicit methods below, it all works fine.
    public static implicit operator int(MyInt myInt)
    {
        return myInt.Value;
    }
    public static implicit operator MyInt(int i)
    {
        return new MyInt() { Value = i };
    }

    public override bool Equals(object obj)
    {
        if (obj is MyInt myInt)
        {
            return this.Value == myInt.Value;
        }
        else
        {
            int other_int = (int)obj;
            return Value == other_int;
        }
    }
}

下面是测试代码,显示了我在定义两个隐式方法时遇到的编译错误。

public class Test
{
    [Fact]
    public void EqualityTest()
    {
        MyInt myInt = new MyInt();
        myInt.Value = 4 ;

        Assert.Equal(4, myInt.Value);   //Always OK which makes sense

        //Compile errors when both implicit methods defined:
        //  Error CS1503  Argument 1: cannot convert from 'int' to 'string', 
        //  Error CS1503  Argument 2: cannot convert from 'ImplicitConversion.MyInt' to 'string'
        Assert.Equal(4, myInt);
    }
}

我相信 C# 抱怨无法将这两种类型都转换为字符串,因为这是最后一个 Xunit.Assert.Equal() 重载的类型,而所有其他类型都无法匹配:

    //Xunit.Assert.Equal methods:
    public static void Equal<T>(T expected, T actual);
    public static void Equal(double expected, double actual, int precision);
    public static void Equal<T>(T expected, T actual, IEqualityComparer<T> comparer);
    public static void Equal(decimal expected, decimal actual, int precision);
    public static void Equal(DateTime expected, DateTime actual, TimeSpan precision);
    public static void Equal<T>(IEnumerable<T> expected, IEnumerable<T> actual, IEqualityComparer<T> comparer);
    public static void Equal<T>(IEnumerable<T> expected, IEnumerable<T> actual);
    public static void Equal(string expected, string actual, bool ignoreCase = false, bool ignoreLineEndingDifferences = false, bool ignoreWhiteSpaceDifferences = false);
    public static void Equal(string expected, string actual);

我认为隐式转换没有出错,因为我可以让其他 similar examples 在与 32 位整数一起使用时产生相同的问题。

我正在一个 .NET Core 3.0 项目中进行测试。

任何帮助将不胜感激。谢谢!

说明: 我想知道的是为什么这只对 32 位整数失败。当类型是其他类型时,隐式转换正在工作(通过调试确认),如下例使用long

using Xunit;

public class MyLong
{
    public long Value;

    public static implicit operator long(MyLong myInt)
    {
        return myInt.Value;
    }
    public static implicit operator MyLong(long i)
    {
        return new MyLong() { Value = i };
    }

    public override bool Equals(object obj)
    {
        if (obj is MyLong myInt)
        {
            return this.Value == myInt.Value;
        }
        else
        {
            long other_int = (long)obj;
            return Value == other_int;
        }
    }
}

public class Test2
{
    [Fact]
    public void EqualityTest()
    {
        MyLong myLong = new MyLong();
        myLong.Value = 4 ;
        Assert.Equal(4, myLong); //NOTE! `4` is implicitly converted to a MyLong 
                                 //object for comparison. Confirmed with debugging.
    }
}

【问题讨论】:

  • 试试Assert.Equal&lt;int&gt;(4, myInt);。我相信编译器在尝试推断泛型参数时不会考虑隐式运算符,因此会发现 (string, string) 是“最接近”的重载。
  • 嗨@DStanley。 Assert.Equal&lt;int&gt;(4, myInt); 确实有效,但并不理想。我在调试时确认编译器正在考虑隐式运算符。我在问题正文中添加了另一个示例,类型为long。在步骤Assert.Equal(4, myLong);,编译器将文字整数4 转换为MyLong 对象(通过调试确认)。为什么它会因 32 位整数而失败?

标签: c# generics implicit-conversion


【解决方案1】:

与循环隐式转换有关吗?

是的(不过,您已经证明了这一点,在消除其中一个转换时它可以正常工作)。

int 发生这种情况的原因,而不是其他类型,是因为您的文字的类型 int。这意味着在重载决议期间,编译器可以采用任何一种方式:将int 转换为MyInt,或将MyInt 转换为int。没有一个选项显然比另一个“更好”,因此这些转换都没有经过考虑。

然后,在排除了该方法最接近的可能泛型版本后,在剩余的可用重载中,唯一剩下的是Equal(string, string) 重载(唯一剩下的只有两个参数的是Equal&lt;T&gt;(IEnumerable&lt;T&gt;, IEnumerable&lt;T&gt;),即根据重载解决规则,比Equal(string, string) 重载“更糟”)。找到一种明显比其他任何方法都“更好”的方法后,编译器会尝试将该方法与您的参数一起使用,这当然不适合,从而导致发出错误。

另一方面……

当您尝试调用Equal(4, myLong) 时,您有两种不兼容的类型。具有int 类型和MyLong 值的文字。在这种情况下,编译器会一一尝试每个参数,发现当它使用MyLong 类型作为类型参数时,可以将int 文字提升为long,然后将其隐式转换为@ 987654336@。但它不能走另一条路。无法选择int 作为泛型类型参数,因为MyLong 无法隐式转换为int。因此,在这种情况下, 有一个“更好”的重载可供选择,因此它被选中了。

通过显式指定字面量的类型,您可以尝试不同的组合并查看此模式的工作原理。首先,我更喜欢使用更简单的包装类进行测试:

public class Wrapper<T>
{
    public T Value;

    public static implicit operator T(Wrapper<T> wrapper) => wrapper.Value;
    public static implicit operator Wrapper<T>(T value) => new Wrapper<T> { Value = value };
}

然后试试这些:

Wrapper<int> w1 = new Wrapper<int> { Value = 4 };
Wrapper<long> w2 = new Wrapper<long> { Value = 4 };

Assert.Equal(4, w1);        // error
Assert.Equal((short)4, w1); // no error
Assert.Equal(4, w2);        // no error
Assert.Equal(4L, w2);       // error

唯一让int 与众不同的是它是数字文字的默认类型。否则,包装 int 的类型与包装其他任何内容的类型完全相同。只要在两个参数之间只能在一个方向上进行转换,一切都很好。但是当双向都可以转换时,编译器只好举手放弃。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-10
    • 2017-03-05
    • 2019-10-19
    • 2014-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多