C# Func 中的歧义扩展方法 +拉姆达

发布于 2024-08-30 06:20:23 字数 1958 浏览 9 评论 0 原文

我一直在尝试阅读这篇文章:

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<> 的方式有关?允许你参数化返回类型吗?我不知道。

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

帮助!

I've been trying to make my way through this article:

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

... And something on page 1 made me uncomfortable. In particular, I was trying to wrap my head around the Compose<>() function, and I wrote an example for myself. Consider the following two Func's:

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

No problem! It's easy to understand what these two do.

Now, following the example from the article, you can write a generic extension method to compose these functions, like so:

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. So now you can say:

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

And you get the string "50%"

So far, so good.

But there's something ambiguous here. Let's say you write another extension method, so now you have two functions:

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

Herein is the ambiguity. Don't these two function have overlapping signatures? Yes. Does this even compile? Yes. Which one get's called? The second one (which clearly gives you the "wrong" result) gets called. If you comment out either function, it still compiles, but you get different results.

It seems like nitpicking, but there's something that deeply offends my sensibilities here, and I can't put my finger on it. Does it have to do with extension methods? Does it have to do with lambdas? Or does it have to do with how Func<> allows you to parameterize the return type? I'm not sure.

I'm guessing that this is all addressed somewhere in the spec, but I don't even know what to Google to find this.

Help!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

临风闻羌笛 2024-09-06 06:20:23

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

如果您创建一个 Func,以及另一个 Func,请在显式指定 ,编译器有足够的信息来确定第二个版本将是完全匹配的,因此它是所使用的版本。

但考虑一下这个愚蠢的例子:

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

哪个版本被调用?结果如何?第一个版本,输出是日期作为与其自身连接的字符串。

另一方面,如果您有一个使用 FuncFunc 的更现实的示例,并且没有明确调用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);

这是不明确的(因此无法编译)。

There's nothing ambiguous here. The second one will get called whenever it is an exact match. Whenever the match is not exact, you get the first function, because by default it is going to be an exact match for everything else.

If you create a Func<double, string>, and another that is Func<double, double>, call .Compose while explicitly specifying <double, double, string>, the compiler has enough information to determine that the second version is going to be an exact match and therefore it is the one used.

But consider this foolish example:

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

Which version gets called? What's the result? The first version, and the output is the date as a string concatenated to itself.

On the other hand, if you had a more realistic example using Func<double, string> and Func<double, double> and weren't as explicit with the call to Compose, which version gets called?

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

The first one, because the compiler determines it to be the exact match versus the second. Since I'm not Eric Lippert or Jon Skeet, I'll not even attempt to explain the binding on that one.


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

...

DoSomething(1, 1);

This is ambiguous (and doesn't compile because of it).

乄_柒ぐ汐 2024-09-06 06:20:23

我不明白这与委托、泛型或扩展方法有什么关系;您的根本问题似乎是重载(特别是当有多个候选者时的方法重载解析)。考虑一下:

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

尽管这是重载解析的候选方法,但采用字符串的重载是完全匹配的,并且是首选方法,也会发生同样的情况。

I don't see what this has to do with delegates, generics, or extension methods; your fundamental problem seems to be with overloading (and specifically, method overload resolution when there are multiple candidates). Consider:

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

}

In Main, which method gets called? Is there ambiguity? Overload resolution kicks during compilation and one method is determined to be a better fit (the method taking a string). Just as in your case, commenting out the first overload will result in code which compiles but calls the other overload. The same thing happens if the second method is defined as:

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

Although this is a candidate for overload resolution, the overload taking a string is an exact match and is preferred.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文