Implicit conversion when calling an extension method not possible
Asked Answered
B

3

7

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?

Benbow answered 28/5, 2018 at 14:47 Comment(7)
Extensions do not convert. They extend a specific type. See this link for more info.Griffiths
I don't think the compiler was built to call extension methods. An action like that might be deemed unsafe by the compiler writers.Flotation
You either have to cast it to decimal ((decimal)i).ToNumberString() or create a new overload for the extension method with an int.Mcallister
I know what I can do, to get this working, but I want to know the technical reason behind this.Benbow
Wouldn't it be very confusing if you were able to call all methods of all object that the current object has an implicit cast to? What if multiple of them have the same signature?Varro
@Varro It picks the best match. That's the case with instance methods.Benbow
@Benbow I said identical.Varro
K
7

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.)

Kerby answered 28/5, 2018 at 15:4 Comment(10)
Interesting, even though I don't understand it completely. Can you give a sample of an eligible implicit conversion?Benbow
@VSDekar: I've edited the answer - reference conversions are probably the most common here.Kerby
Why can't you get boxing conversions to work? Just define extension on this object and call it on int - boxing conversion.Average
@Evk: In my test program I'd forgotten to add the this modifier to the parameter in the extension method :) I've now included the example.Kerby
Thanks for your well written answer. I have learned something today! :-)Benbow
@DaisyShipton Ok... I think I understand now more or less why we cannot use implicit conversion on extension methods. But don't we have the very same problems on instance methods? Why is implicit conversion allowed on instance methods?Benbow
@VSDekar: Only certain implicit conversions are allowed on method call targets, too. For example, there's an implicit conversion from string to XName, and XName has a Get(string) instance method - but you can't write "ns".Get("name"). It's entirely possible that the set of valid conversions for method call targets is the set of valid conversions for extension methods; I'd have to check.Kerby
@DaisyShipton I'm quite sure it is. Reference conversion is in play, for example, when you call method defined in parent class, and boxing - when you, for example, call ToString() on struct which does not override it.Average
@DaisyShipton Maybe I don't understand everything since English is not my native language, but what is with the static method in my sample ToNumberString2(decimal d)? There the implicit conversion is allowed, but I think we face the same problems like we have with extension methods... So i still don't get why implicit conversion is allowed for "normal" methods but not for extension methods.Benbow
@VSDekar: That's an implicit conversion for an argument to the method, not for the target of the method call. That's the difference. Other implicit conversions are allowed for all but the first parameter of an extension method - but extension methods are meant to "feel" as if you're calling them on that first parameter. Even though really the "target" of the extension method is a regular first argument as far as the CLR is concerned, the language is designed to treat it similarly to targets of instance methods.Kerby
F
0

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());
 }
Fresher answered 8/7 at 7:13 Comment(0)
G
-4

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");
        }
    }
}
Gati answered 28/5, 2018 at 14:58 Comment(2)
this is not the answer to the question. while technically correct.Horripilate
The question is why the compiler does not use the decimal extension with an implicit conversion, like it does when using instance methods.Vesperal

© 2022 - 2024 — McMap. All rights reserved.