Pernyataan sintaksis return ganjil

106

Saya tahu ini mungkin terdengar aneh tetapi saya bahkan tidak tahu bagaimana mencari sintaks ini di internet dan juga saya tidak yakin apa sebenarnya artinya.

Jadi saya telah mengawasi beberapa kode MoreLINQ dan kemudian saya memperhatikan metode ini

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

Apa pernyataan ganjil ini? return _();?

kuskmen
sumber
6
Atau maksud Anda: return _(); IEnumerable<TSource> _()?
Alex K.
6
@ Steve, saya ingin tahu apakah OP lebih mengacu pada return _(); IEnumerable<TSource> _()daripada yield return?
Rob
5
Saya pikir yang dia maksud adalah baris ini return _(); IEnumerable<TSource> _(). Dia bisa bingung dengan tampilannya daripada pernyataan pengembalian yang sebenarnya.
Mateusz
5
@AkashKava OP mengatakan ada pernyataan pengembalian yang aneh. Sayangnya, kode tersebut berisi dua pernyataan pengembalian. Jadi bisa dimaklumi jika orang bingung apa yang dia maksud.
mjwills
5
Mengedit pertanyaan, dan sekali lagi mohon maaf atas kebingungannya.
kuskmen

Jawaban:

106

Ini adalah C # 7.0 yang mendukung fungsi lokal ....

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 # saat ini dengan 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(); 

    }

Triknya adalah, _ () dideklarasikan setelah digunakan, yang tidak masalah.

Penggunaan praktis fungsi lokal

Contoh di atas hanyalah demonstrasi tentang bagaimana metode inline dapat digunakan, tetapi kemungkinan besar jika Anda akan memanggil metode hanya sekali, maka itu tidak ada gunanya.

Tetapi dalam contoh di atas, seperti yang disebutkan dalam komentar oleh Phoshi dan Luaan , ada keuntungan menggunakan fungsi lokal. Karena fungsi dengan hasil pengembalian tidak akan dijalankan kecuali seseorang mengulanginya, dalam kasus ini metode di luar fungsi lokal akan dijalankan dan validasi parameter akan dilakukan bahkan jika tidak ada yang akan mengulang nilainya.

Berkali-kali kita mengulangi kode dalam metode, mari kita lihat contoh ini ..

  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... 
  }

Saya bisa mengoptimalkan ini dengan ...

  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... 
  }
Akash Kava
sumber
4
@ZoharPeled Well .. kode diposting tidak menunjukkan penggunaan untuk fungsi .. :)
Rob
2
@ColinM salah satu keuntungannya adalah bahwa fungsi anonim dapat dengan mudah mengakses variabel dari 'inangnya'.
mjwills
6
Apakah Anda yakin bahwa dalam C # -speak ini sebenarnya disebut fungsi anonim? Tampaknya memiliki nama, yaitu _AnonymousFunctionatau hanya _, sementara saya mengharapkan fungsi anonim asli menjadi seperti itu (x,y) => x+y. Saya akan menyebutnya fungsi lokal, tapi saya tidak terbiasa dengan terminologi C #.
chi
12
Secara eksplisit, karena tidak ada yang menunjukkannya, cuplikan kode ini menggunakan fungsi lokal karena merupakan iterator (perhatikan hasil), dan karenanya dieksekusi dengan malas. Tanpa fungsi lokal, Anda harus menerima bahwa validasi input terjadi pada penggunaan pertama, atau memiliki metode yang hanya akan dipanggil oleh satu metode lain yang ada di sekitar karena alasan yang sangat kecil.
Phoshi
6
@ColinM Contoh kuksmen yang diposting sebenarnya adalah salah satu alasan utama ini akhirnya diterapkan - ketika Anda membuat fungsi dengan yield return, tidak ada kode yang dieksekusi sampai enumerable benar-benar dihitung. Ini tidak diinginkan, karena Anda ingin misalnya memverifikasi argumen segera. Satu-satunya cara untuk melakukan ini di C # adalah dengan memisahkan metode menjadi dua metode - satu dengan yield returns, dan yang lainnya tanpa. Metode sebaris memungkinkan Anda mendeklarasikan yieldmetode penggunaan di dalam , menghindari kekacauan dan potensi penyalahgunaan metode yang sangat internal bagi induknya dan tidak dapat digunakan kembali.
Luaan
24

Pertimbangkan contoh yang lebih sederhana

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

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

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

_() adalah fungsi lokal yang dideklarasikan dalam metode yang berisi pernyataan return.

Stuart
sumber
3
Ya, saya tahu tentang fungsi lokal, formatnya yang membodohi saya ... harap ini tidak menjadi standar.
kuskmen
20
Apakah maksud Anda deklarasi fungsi dimulai pada baris yang sama? Jika demikian, saya setuju, itu mengerikan!
Stuart
3
Ya, itulah yang saya maksud.
kuskmen
9
Kecuali untuk penamaan itu, garis bawah juga mengerikan
Icepickle
1
@AkashKava: pertanyaannya bukan apakah itu legal C #, tetapi apakah kode itu mudah dimengerti (dan karenanya mudah dipelihara dan enak dibaca) ketika diformat seperti ini. Preferensi pribadi berperan, tapi saya cenderung setuju dengan Stuart.
PJTraill