为什么在调用扩展方法时无法使用隐式转换?
以下是示例代码:
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的隐式转换,当我们不将它用作扩展方法时,它可以正常工作。
我知道我能做些什么来让事情发挥作用, 但是,使用扩展方法时,没有隐式转换的技术原因是什么?
答案 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");
}
}
}