为什么调用扩展方法时不能使用隐式转换?
这是示例代码:
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 到十进制的隐式转换存在,并且当我们不将其用作扩展方法时工作得很好。
我知道我能做些什么来让事情顺利进行, 但是使用扩展方法时不可能进行隐式强制转换的技术原因是什么?
隐式转换允许用于扩展方法调用的目标,但它们受到限制。来自 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
。 (诚然,对类型参数进行约束的泛型方法可能是一个更好的主意。)
您可以定义重载扩展方法
示例:
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());
}
试试这个:
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");
}
}
}