调用扩展方法时不可能进行隐式转换

问题描述 投票:0回答: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");
        }
    }
}

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

正如我们所见。从 int 到十进制的隐式转换存在,并且当我们不将其用作扩展方法时工作得很好。

我知道我能做些什么来让事情顺利进行, 但是使用扩展方法时不可能进行隐式强制转换的技术原因是什么?

c# roslyn
3个回答
6
投票

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

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

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

在您的情况下,涉及的转换不是身份、引用或装箱转换,因此该方法不符合条件。

我们几乎每次使用 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
。 (诚然,对类型参数进行约束的泛型方法可能是一个更好的主意。)


0
投票

您可以定义重载扩展方法

示例:

 public static string ToNumberString(this int input)
 {
     return ToNumberStringBase(input, 0);
 }
 public static string ToNumberString(this short input)
 {
     return ToNumberStringBase(input, 0);
 }
 public static string ToNumberString(this byte input)
 {
     return ToNumberStringBase(input, 0);
 }
 public static string ToNumberString(this long input)
 {
     return ToNumberStringBase(input, 0);
 }
 public static string ToNumberString(this decimal input, int decimals = 0)
 {
     return ToNumberStringBase((double)input, decimals);
 }
 public static string ToNumberString(this float input, int decimals = 0)
 {
     return ToNumberStringBase(input, decimals);
 }
 public static string ToNumberString(this double input, int decimals = 0)
 {
     return ToNumberStringBase(input, decimals);
 }
 private static string ToNumberStringBase(double input, int decimals = 0)
 {
     return input.ToString("N" + decimals.ToString());
 }

-4
投票

试试这个:

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");
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.