Reputation: 1821
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?
Upvotes: 7
Views: 1441
Reputation: 157
You Can Define Overload Extension Methods
Example:
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());
}
Upvotes: 0
Reputation: 1499790
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.)
Upvotes: 7
Reputation: 645
Try this:
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");
}
}
}
Upvotes: -4