Неявное преобразование при вызове метода расширения невозможно - PullRequest
0 голосов
/ 28 мая 2018

Почему невозможно использовать неявное преобразование при вызове метода расширения?

Вот пример кода:

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 в десятичное существует и прекрасно работает, когда мы не используем его в качестве метода расширения.

Я знаю, что я могу сделать, чтобы все заработало, Но какова техническая причина того, что при работе с методами расширения невозможно неявное приведение?

Ответы [ 2 ]

0 голосов
/ 28 мая 2018

Неявные преобразования разрешены для цели вызовов методов расширения, но они ограничены.Из раздела 7.5.6.2 стандарта ECMA C # 5:

Метод расширения 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.(По общему признанию универсальный метод с ограничением на параметр типа, вероятно, был бы лучшей идеей там).

0 голосов
/ 28 мая 2018

Попробуйте это:

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");
        }
    }
}
...