2

我一直在努力完成这篇文章:

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

...第 1 页上的某些内容让我感到不舒服。特别是,我试图围绕 Compose<>() 函数进行思考,并为自己编写了一个示例。考虑以下两个 Func:

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

美好的。所以现在你可以说:

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

这里是歧义。这两个函数没有重叠签名吗?是的。这甚至可以编译吗?是的。哪一个被调用?第二个(显然给你“错误”的结果)被调用。如果您注释掉任何一个函数,它仍然可以编译,但您会得到不同的结果。

这似乎是在吹毛求疵,但这里有一些东西深深地冒犯了我的感情,我不能指望它。它与扩展方法有关吗?它与lambda有关吗?或者它是否与 Func<> 允许您参数化返回类型有关?我不确定。

我猜这一切都在规范中的某个地方得到解决,但我什至不知道谷歌要找到什么。

帮助!

4

2 回答 2

6

这里没有任何含糊之处。只要精确匹配,就会调用第二个。每当匹配不准确时,您将获得第一个函数,因为默认情况下它将与其他所有内容完全匹配。

如果您创建一个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>andFunc<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));

第一个,因为编译器确定它与第二个完全匹配。由于我不是 Eric Lippert 或 Jon Skeet,我什至不会试图解释那个绑定。


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

...

DoSomething(1, 1);

是模棱两可的(因此无法编译)。

于 2010-04-24T05:45:25.200 回答
0

我看不出这与委托、泛型或扩展方法有什么关系。您的基本问题似乎与重载有关(特别是当有多个候选者时,方法重载解决方案)。考虑:

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()); 
    }

尽管这是重载解决方案的候选,但采用字符串的重载是完全匹配的并且是首选。

于 2010-04-26T14:09:11.347 回答