无法调用扩展方法时的隐式转换

时间:2018-05-28 14:47:56

标签: c# roslyn

为什么在调用扩展方法时无法使用隐式转换?

以下是示例代码:

using System;

namespace IntDecimal
{
    class Program
    {
        static void Main(string[] args)
        {
            decimal d = 1000m;
            int i = 1000;

            d = i; // implicid conversion works just fine

            Console.WriteLine(d.ToNumberString()); // Works as expected
            Console.WriteLine(i.ToNumberString()); // Error
            Console.WriteLine(ToNumberString2(i)); // Implicid conversion here works just fine
        }

        static string ToNumberString2(decimal d)
        {
            return d.ToString("N0");
        }
    }

    public static class Ext
    {
        public static string ToNumberString(this decimal d)
        {
            return d.ToString("N0");
        }
    }
}

我得到的错误: 'int'不包含'ToNumberString'的定义,最好的扩展方法重载'Ext.ToNumberString(decimal)'需要'decimal'类型的接收器

我们可以看到。存在从int到decimal的隐式转换,当我们不将它用作扩展方法时,它可以正常工作。

我知道我能做些什么来让事情发挥作用, 但是,使用扩展方法时,没有隐式转换的技术原因是什么?

2 个答案:

答案 0 :(得分:6)

允许扩展方法调用目标的隐式转换 ,但它们受到限制。从ECMA C#5标准的第7.5.6.2节开始:

  

如果符合以下条件,则扩展方法 Ci.Mj 符合条件:

     
      
  • ...
  •   
  • expr Mj 的第一个参数的类型存在隐式标识,引用或装箱转换。
  •   

在您的情况下,涉及的转换不是身份,参考或拳击转换,因此该方法不符合条件。

我们几乎每次使用LINQ时都使用符合条件的突变。例如:

List<string> names = new List<string> { "a", "b" };
IEnumerable<string> query = names.Select(x => x.ToUpper());

此方法的目标是IEnumerable<T>,但参数类型为List<string>T被推断为string,但仍需要从List<string>IEnumerable<string>进行转换。但这是允许的,因为它是引用转换

我可以看到为什么规则存在于参考类型,至少。假设我们有一个可变引用类型X,它隐式转换为另一个可变引用类型Y。定位Y的扩展方法会使其发生变异,这会非常混乱,因为它可能不会改变原始的X。扩展方法意味着“感觉”就像它们对原始值的行为一样,并且当允许转换而不是列出的转换时不是这种情况。

即使是拳击转换对我来说也有点怀疑。这是一个例子,使用实现接口的可变结构:

using System;

public interface IMutable
{
    void Mutate();
}

public static class MutationExtensions
{
    public static void CallMutate(this IMutable target)
    {
        target.Mutate();
    }
}

public struct MutableStruct : IMutable
{
    public int value;

    public void Mutate()
    {
        value++;
    }
}

class Program
{
    static void Main()
    {
        MutableStruct x = new MutableStruct();
        Console.WriteLine(x.value); // 0
        x.Mutate();
        Console.WriteLine(x.value); // 1
        x.CallMutate();
        Console.WriteLine(x.value); // 1
    }
}

最后一个结果(1,而不是2)是因为该值被装箱为IMutable,并且只修改了该框 - 而不是x变量。

我怀疑像这样的角落案件被认为是“可接受的令人讨厌的”,因为能够为值类型可能实现的其他接口编写扩展方法,例如IFormattable。 (无论如何,对类型参数有约束的通用方法可能会更好。)

答案 1 :(得分:-3)

试试这个:

using System;

namespace IntDecimal
{
    class Program
    {
        static void Main(string[] args)
        {
            decimal d = 1000m;
            int i = 1000;

            d = i; // implicid conversion works just fine

            Console.WriteLine(d.ToNumberString()); // Works as expected
            Console.WriteLine(i.ToNumberString()); // Error
            Console.WriteLine(ToNumberString2(i)); // Implicid conversion here works just fine
        }

        static string ToNumberString2(decimal d)
        {
            return d.ToString("N0");
        }
    }

    public static class Ext
    {
        public static string ToNumberString(this decimal d)
        {
            return d.ToString("N0");
        }

        public static string ToNumberString(this int d)
        {
            return d.ToString("N0");
        }
    }
}