Why is it not possible to use implicit conversion when calling an extension method?
Here is the sample code:
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");
}
}
}
Error i get: 'int' does not contain a definition for 'ToNumberString' and the best extension method overload 'Ext.ToNumberString(decimal)' requires a receiver of type 'decimal'
As we can see. An implicit conversion from int to decimal exists and works just fine when we do not use it as an extension method.
I know what I can do to get things working, But what is the technical reason that there is no implicit cast possible when working with extension methods?
To define and call the extension methodDefine a static class to contain the extension method. The class must be visible to client code. For more information about accessibility rules, see Access Modifiers. Implement the extension method as a static method with at least the same visibility as the containing class.
You can use extension methods to extend a class or interface, but not to override them. An extension method with the same name and signature as an interface or class method will never be called. At compile time, extension methods always have lower priority than instance methods defined in the type itself.
An extension method must be a static method. An extension method must be inside a static class -- the class can have any name. The parameter in an extension method should always have the "this" keyword preceding the type on which the method needs to be called.
According to Microsoft, In fact, extension methods cannot access private variables in the type they are extending.
Implicit conversions are allowed for the target of extension method invocations, but they're restricted. From section 7.5.6.2 of the ECMA C# 5 standard:
An extension method Ci.Mj is eligible if:
- ...
- An implicit identity, reference or boxing conversion exists from expr to the type of the first parameter of Mj.
In your case, the conversion involved is not an identity, reference or boxing conversion, so the method is not eligible.
We use eligible mutations almost every time we use LINQ. For example:
List<string> names = new List<string> { "a", "b" };
IEnumerable<string> query = names.Select(x => x.ToUpper());
Here the target of the method is IEnumerable<T>
, but the argument type is List<string>
. T
is inferred to be string
, but there's still a conversion required from List<string>
to IEnumerable<string>
. That's permitted though, because it's a reference conversion.
I can see why the rule exists for reference types, at least. Suppose we had a mutable reference type X
with an implicit conversion to another mutable reference type Y
. An extension method targeting Y
that mutated it would be very confusing, because it would presumably not mutate the original X
. Extension methods are meant to "feel" like they're acting on the original value, and that isn't the case when conversions other than the ones listed are permitted.
Even boxing conversions feel slightly dubious to me. Here's an example, using a mutable struct that implements an interface:
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
}
}
That last result (1, not 2) is because the value was boxed to be an IMutable
, and only the box was modified - not the x
variable.
I suspect corner cases like this were deemed "acceptably nasty" bearing in mind the benefit of being able to write extension methods for other interfaces that value types might implement, such as IFormattable
. (Admittedly a generic method with a constraint on the type parameter would probably be a better idea there anyway.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With