奇数返回语法语句


106

我知道这听起来很奇怪,但我什至不知道如何在互联网上搜索此语法,而且我也不知道这到底意味着什么。

所以我看了一些MoreLINQ代码,然后我注意到了这种方法

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));

    return _(); IEnumerable<TSource> _()
    {
        var knownKeys = new HashSet<TKey>(comparer);
        foreach (var element in source)
        {
            if (knownKeys.Add(keySelector(element)))
                yield return element;
        }
    }
}

这个奇怪的回报表是什么?return _();


6
还是您的意思是:return _(); IEnumerable<TSource> _()
Alex K.

6
@Steve,我想知道OP是否return _(); IEnumerable<TSource> _()比指的更多yield return
罗布

5
我认为他的意思是这条线return _(); IEnumerable<TSource> _()。他可能会对它的外观感到困惑,而不是对实际的return语句感到困惑。
Mateusz

5
@AkashKava OP说有一个奇怪的返回声明。不幸的是,该代码包含两个 return语句。因此,如果人们对他/她指的是什么感到困惑,这是可以理解的。
mjwills

5
编辑了问题,再次为造成的困惑感到抱歉。
kuskmen '17

Answers:


106

这是支持本地功能的C#7.0。

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
       this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        if (source == null) throw new 
           ArgumentNullException(nameof(source));
        if (keySelector == null) throw 
             new ArgumentNullException(nameof(keySelector));

        // This is basically executing _LocalFunction()
        return _LocalFunction(); 

        // This is a new inline method, 
        // return within this is only within scope of
        // this method
        IEnumerable<TSource> _LocalFunction()
        {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
        }
    }

当前的C#与 Func<T>

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
       this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        if (source == null) throw new 
           ArgumentNullException(nameof(source));
        if (keySelector == null) throw 
             new ArgumentNullException(nameof(keySelector));

        Func<IEnumerable<TSource>> func = () => {
            var knownKeys = new HashSet<TKey>(comparer);
            foreach (var element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                    yield return element;
            }
       };

        // This is basically executing func
        return func(); 

    }

诀窍是,使用后会声明_(),这非常好。

实际使用局部功能

上面的示例只是一个演示如何使用内联方法的示例,但是很可能如果您只调用一次方法,那么它就没有用了。

但是在上面的示例中,如PhoshiLuaan的评论中所述,使用局部函数有一个优势。因为除非有人对其进行迭代,否则不会执行带有yield return的函数,所以在这种情况下,即使没有人迭代该值,也将执行局部函数之外的方法并执行参数验证。

很多时候我们在方法中重复了代码,让我们看一下这个例子。

  public void ValidateCustomer(Customer customer){

      if( string.IsNullOrEmpty( customer.FirstName )){
           string error = "Firstname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      if( string.IsNullOrEmpty( customer.LastName )){
           string error = "Lastname cannot be empty";
           customer.ValidationErrors.Add(error);
           ErrorLogger.Log(error);
           throw new ValidationError(error);
      }

      ... on  and on... 
  }

我可以用...优化它

  public void ValidateCustomer(Customer customer){

      void _validate(string value, string error){
           if(!string.IsNullOrWhitespace(value)){

              // i can easily reference customer here
              customer.ValidationErrors.Add(error);

              ErrorLogger.Log(error);
              throw new ValidationError(error);                   
           }
      }

      _validate(customer.FirstName, "Firstname cannot be empty");
      _validate(customer.LastName, "Lastname cannot be empty");
      ... on  and on... 
  }

4
@ZoharPeled好..所发布的代码确实显示了该功能的使用..::)
Rob

2
@ColinM的好处之一是匿名函数可以轻松地从其“主机”访问变量。
mjwills

6
您确定在C#语言中实际上称为匿名函数吗?它似乎有一个名称,即_AnonymousFunction_,而我希望真正的匿名函数类似于(x,y) => x+y。我将其称为局部函数,但我不习惯C#术语。

12
明确地说,正如似乎没有人指出的那样,此代码段使用本地函数,因为它是迭代器(请注意yield),因此执行延迟。如果没有局部函数,您将需要要么接受输入验证在首次使用时就发生,要么接受一个方法,该方法只有很少的理由被另一个方法调用。
Phoshi

6
@ColinM kuksmen发布的示例实际上是最终实现该示例的主要原因之一-当您使用制作函数时yield return,直到实际枚举可枚举之前,都不会执行任何代码。这是不希望的,因为您想立即验证参数。在C#中执行此操作的唯一方法是将方法分为两种方法-一种使用yield returns,另一种不用。内联方法使您可以在内部声明yieldusing方法,避免混乱和潜在地滥用严格在其父级内部且不可重用的方法。
a安

24

考虑更简单的例子

void Main()
{
    Console.WriteLine(Foo()); // Prints 5
}

public static int Foo()
{
    return _();

    // declare the body of _()
    int _()
    {
        return 5;
    }
}

_() 是在包含return语句的方法内声明的局部函数。


3
是的,我知道局部函数,这是欺骗我的格式...希望这不会成为标准。
kuskmen '17

20
您是说函数声明从同一行开始吗?如果是这样,我同意,这太可怕了!
斯图尔特

3
是的,这就是我的意思。
kuskmen '17

9
除了下划线,它的下划线也很可怕
Icepickle '17

1
@AkashKava:问题不在于它是否是合法的C#,而是当这样格式化时,代码是否易于理解(因此易于维护并易于阅读)。个人喜好发挥作用,但我倾向于同意Stuart的观点。
PJTraill '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.