C # неоднозначность в Func + методы расширения + лямбда-выражения - PullRequest
2 голосов
/ 24 апреля 2010

Я пытался пробиться сквозь эту статью:

http://blogs.msdn.com/wesdyer/archive/2008/01/11/the-marvels-of-monads.aspx

... И что-то на странице 1 заставило меня чувствовать себя неловко. В частности, я пытался обернуть голову вокруг функции Compose <> (), и я написал пример для себя. Рассмотрим следующие две функции:

Func<double, double> addTenth = x => x + 0.10;
Func<double, string> toPercentString = x => (x * 100.0).ToString() + "%";

Нет проблем! Легко понять, что делают эти двое.

Теперь, следуя примеру из статьи, вы можете написать универсальный метод расширения для составления этих функций, например так:

public static class ExtensionMethods
{
 public static Func<TInput, TLastOutput> Compose<TInput, TFirstOutput, TLastOutput>(
  this Func<TFirstOutput, TLastOutput> toPercentString,
  Func<TInput, TFirstOutput> addTenth)
 {
  return input => toPercentString(addTenth(input));
 }
}

Fine. Так что теперь вы можете сказать:

string x = toPercentString.Compose<double, double, string>(addTenth)(0.4);

И вы получите строку "50%"

Пока все хорошо.

Но здесь есть что-то неоднозначное. Допустим, вы пишете другой метод расширения, так что теперь у вас есть две функции:

public static class ExtensionMethods
{
 public static Func<TInput, TLastOutput> Compose<TInput, TFirstOutput, TLastOutput>(
  this Func<TFirstOutput, TLastOutput> toPercentString,
  Func<TInput, TFirstOutput> addTenth)
 {
  return input => toPercentString(addTenth(input));
 }

 public static Func<double, string> Compose<TInput, TFirstOutput, TLastOutput>(this
  Func<double, string> toPercentString,
  Func<double, double> addTenth)
 {
  return input => toPercentString(addTenth(input + 99999));
 }
}

В этом двусмысленность. Разве эти две функции не имеют перекрывающихся подписей? Да. Это даже компилируется? Да. Какой из них называется? Второй (который явно дает вам «неправильный» результат) вызывается. Если вы закомментируете какую-либо функцию, она все еще компилируется, но вы получите другие результаты.

Это похоже на придирку, но есть кое-что, что глубоко оскорбляет мою чувствительность, и я не могу это понять. Это имеет отношение к методам расширения? Связано ли это с лямбдами? Или это связано с тем, как Func <> позволяет параметризовать тип возвращаемого значения? Я не уверен.

Я предполагаю, что все это указано где-то в спецификации, но я даже не знаю, что Google может найти это.

Помощь!

Ответы [ 2 ]

6 голосов
/ 24 апреля 2010

Здесь нет ничего двусмысленного. Второй будет вызываться всякий раз, когда будет точное совпадение. Всякий раз, когда совпадение не является точным, вы получаете первую функцию, потому что по умолчанию это будет точное совпадение для всего остального.

Если вы создаете Func<double, string>, а другой - Func<double, double>, вызываете .Compose при явном указании <double, double, string>, у компилятора достаточно информации, чтобы определить, что вторая версия будет точно соответствовать, и поэтому это тот, который используется.

Но рассмотрим этот глупый пример:

Func<string, string> doubleString = s => s + s;
Func<DateTime, string> dateToString = date => date.ToString();

Func<DateTime, string> composedFunction = doubleString.Compose(dateToString);
Console.WriteLine(composedFunction(DateTime.Now));

Какая версия вызывается? Каков результат? Первая версия, а вывод - дата в виде строки, соединенной с самим собой.

С другой стороны, если у вас был более реалистичный пример с использованием Func<double, string> и Func<double, double> и вы не были столь явными при вызове Compose, какая версия вызывается?

Func<double, string> toPercentString = d => d.ToString("0.0%");
Func<double, double> addTenth = d => d + 0.1;
Console.WriteLine(toPercentString.Compose(addTenth)(0.8));

Первый, потому что компилятор определяет его как точное совпадение со вторым. Поскольку я не Эрик Липперт или Джон Скит, я даже не буду пытаться объяснить привязку к этому.


static void DoSomething(float f, double d) { }
static void DoSomething(double d, float f) { }

...

DoSomething(1, 1);

Это неоднозначно (и не компилируется из-за этого).

0 голосов
/ 26 апреля 2010

Я не вижу, как это связано с делегатами, обобщениями или методами расширения; Ваша основная проблема, кажется, связана с перегрузкой (и, в частности, с разрешением перегрузки метода, когда есть несколько кандидатов). Рассмотрим:

public static class Test {
    public static void Method(string s) { 
        Console.WriteLine("String version: " + s); 
    }
    public static void Method(object o) { 
        Console.WriteLine("Object version: " + o.ToString()); 
    }

    public static void Main(string[] args) { Method("my string"); }

}

В Main, какой метод вызывается? Есть ли двусмысленность? Разрешение перегрузки срабатывает во время компиляции, и определено, что один метод лучше подходит (метод принимает строку). Как и в вашем случае, комментирование первой перегрузки приведет к коду, который компилируется, но вызывает другую перегрузку. То же самое происходит, если второй метод определен как:

    public static void Method<T>(T t) { 
        Console.WriteLine("Generic version: " + t.ToString()); 
    }

Хотя это является кандидатом для разрешения перегрузки, перегрузка, принимающая строку, является точным совпадением и является предпочтительной.

...