奇数の戻り構文ステートメント


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ステートメントは何ですか?return _();


6
または、どういう意味return _(); IEnumerable<TSource> _()ですか?
Alex K.17年

6
@スティーブ、私はOPがより多くを参照してreturn _(); IEnumerable<TSource> _()いるのyield returnだろうか?
Rob

5
私は彼がこのラインを意味したと思いますreturn _(); IEnumerable<TSource> _()。実際のreturnステートメントではなく、見た目が混乱する可能性があります。
Mateusz 2017

5
@AkashKava OPは、奇妙なリターンステートメントがあると述べました。残念ながら、コードには2つの returnステートメントが含まれています。ですから、彼/彼女がどちらに言及しているかについて人々が混乱しているなら、それは理解できます。
mjwills 2017

5
質問を編集しましたが、混乱を招きました。
kuskmen 2017

回答:


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

    }

コツは、使用後に_()を宣言することですが、これはまったく問題ありません。

ローカル関数の実用的な使用

上記の例は、インラインメソッドの使用方法のデモンストレーションですが、メソッドを1回だけ呼び出す場合は、ほとんど役に立ちません。

しかし、上記の例では、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 Well ..投稿されたコード関数の使用法を示しています.. :)
Rob

2
@ColinMの利点の1つは、無名関数がその「ホスト」から変数に簡単にアクセスできることです。
mjwills 2017

6
C#で言うと、これは実際には無名関数と呼ばれていますか?それは名前を持っているようです。つまり_AnonymousFunction、単なる名前の_よう(x,y) => x+yですが、本物の匿名関数はのようになると思います。これをローカル関数と呼びますが、C#の用語には慣れていません。
2017

12
明確に言うと、誰も指摘していないようですが、このコードスニペットはイテレータ(yieldに注意)であるため、ローカル関数を使用しているため、遅延して実行されます。ローカル関数がないと、最初の使用時に入力の検証が行われることを受け入れるか、非常に小さな理由で横になっている他の1つのメソッドによってのみ呼び出されるメソッドを持つ必要があります。
Phoshi 2017

6
@ColinM投稿されたkuksmenの例は、実際にこれが最終的に実装された主な理由の1つです。で関数を作成するとyield return、列挙型が実際に列挙されるまでコードは実行されません。たとえば、引数をすぐに検証したいので、これは望ましくありません。これをC#で行う唯一の方法は、メソッドを2つのメソッドに分離することです。1つはyield returnsあり、もう1つはなしです。インラインメソッドを使用すると、yieldusingメソッドを宣言できるため、親の内部でのみ使用され、再利用できないメソッドの混乱や潜在的な誤用を回避できます。
Luaan 2017

24

より簡単な例を考えてみましょう

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

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

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

_() returnステートメントを含むメソッド内で宣言されたローカル関数です。


3
はい、私はローカル関数について知っています。それは私をだましたフォーマットでした...これが標準にならないことを願っています。
kuskmen 2017

20
同じ行で始まる関数宣言を意味しますか?もしそうなら、私は同意する、それは恐ろしいです!
スチュアート

3
はい、そういうことです。
kuskmen 2017

9
その名前を除いて、下線もひどいです
Icepickle

1
@AkashKava:問題は、それが合法的なC#であるかどうかではなく、このようにフォーマットされたときにコードが理解しやすい(したがって保守が容易で読みやすい)かどうかです。個人的な好みが役割を果たすが、私はスチュアートに同意する傾向がある。
PJTraill 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.