Câu lệnh cú pháp trả về kỳ lạ


106

Tôi biết điều này nghe có vẻ lạ nhưng tôi thậm chí không biết cách tìm kiếm cú pháp này trên internet và tôi cũng không chắc chính xác nghĩa là gì.

Vì vậy, tôi đã xem qua một số mã MoreLINQ và sau đó tôi nhận thấy phương pháp này

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

Câu lệnh trả về kỳ lạ này là gì? return _();?


6
Hay ý bạn là return _(); IEnumerable<TSource> _():?
Alex K.

6
@Steve, tôi tự hỏi liệu OP có đang đề cập đến nhiều return _(); IEnumerable<TSource> _()hơn là yield return?
Rob

5
Tôi nghĩ anh ấy có ý nói dòng này return _(); IEnumerable<TSource> _(). Anh ta có thể bối rối bởi nó trông giống như thế nào hơn là tuyên bố trả lại thực tế.
Mateusz

5
@AkashKava OP cho biết có một tuyên bố trả lại kỳ lạ. Thật không may, mã chứa hai câu lệnh trả về. Vì vậy, có thể hiểu được nếu mọi người bối rối không biết anh ấy / cô ấy đang đề cập đến điều gì.
mjwills

5
Đã chỉnh sửa câu hỏi, và một lần nữa xin lỗi vì sự nhầm lẫn.
kuskmen

Câu trả lời:


106

Đây là C # 7.0 hỗ trợ các chức năng cục bộ ....

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 # hiện tại với 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(); 

    }

Bí quyết là, _ () được khai báo sau khi nó được sử dụng, điều này hoàn toàn ổn.

Sử dụng thực tế các chức năng địa phương

Ví dụ trên chỉ là một minh chứng về cách có thể sử dụng phương thức nội tuyến, nhưng rất có thể nếu bạn định gọi phương thức chỉ một lần, thì nó không có ích gì.

Nhưng trong ví dụ trên, như đã đề cập trong các bình luận của PhoshiLuaan , có một lợi thế là sử dụng hàm cục bộ. Vì hàm có trả về lợi nhuận sẽ không được thực thi trừ khi ai đó lặp lại nó, trong trường hợp này, phương thức bên ngoài hàm cục bộ sẽ được thực thi và việc xác thực tham số sẽ được thực hiện ngay cả khi không ai lặp lại giá trị.

Nhiều lần chúng tôi đã lặp lại mã trong phương thức, hãy xem ví dụ này ..

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

Tôi có thể tối ưu hóa điều này với ...

  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 Vâng .. mã đăng không hiển thị một sử dụng cho các chức năng .. :)
Rob

2
@ColinM một trong những lợi ích là hàm ẩn danh có thể dễ dàng truy cập các biến từ 'máy chủ' của nó.
mjwills

6
Bạn có chắc rằng trong C # -speak, đây thực sự được gọi là một hàm ẩn danh? Nó dường như có một cái tên, cụ thể là _AnonymousFunctionhoặc chỉ _, trong khi tôi mong đợi một chức năng ẩn danh thực sự giống như thế (x,y) => x+y. Tôi sẽ gọi đây là một hàm cục bộ, nhưng tôi không quen với thuật ngữ C #.
chi

12
Nói một cách rõ ràng, dường như không ai chỉ ra nó, đoạn mã này đang sử dụng hàm cục bộ vì nó là một trình lặp (lưu ý lợi nhuận) và do đó thực thi một cách lười biếng. Nếu không có hàm cục bộ, bạn sẽ cần phải chấp nhận rằng xác thực đầu vào xảy ra trong lần sử dụng đầu tiên, hoặc có một phương thức sẽ chỉ được gọi bởi một phương thức khác vì rất ít lý do.
Phoshi

6
@ColinM Ví dụ về kuksmen được đăng thực sự là một trong những lý do chính khiến điều này cuối cùng đã được thực hiện - khi bạn tạo một hàm với yield return, không có mã nào được thực thi cho đến khi liệt kê thực sự được liệt kê. Điều này là không mong muốn, vì bạn muốn xác minh các đối số ngay lập tức. Cách duy nhất để làm điều này trong C # là tách phương thức thành hai phương thức - một phương thức có yield returns và phương thức kia không có. Các phương thức nội tuyến cho phép bạn khai báo yieldphương thức using bên trong , tránh sự lộn xộn và tiềm ẩn việc lạm dụng một phương thức hoàn toàn nội bộ của nó và không thể sử dụng lại.
Luaan

24

Hãy xem xét ví dụ đơn giản hơn

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

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

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

_() là một hàm cục bộ được khai báo trong phương thức chứa câu lệnh trả về.


3
Có, tôi biết về các chức năng cục bộ, đó là định dạng đã đánh lừa tôi ... hy vọng điều này không trở nên nổi bật.
kuskmen

20
Ý bạn là khai báo hàm bắt đầu trên cùng một dòng? Nếu vậy, tôi đồng ý, thật kinh khủng!
Stuart

3
Vâng, đó là những gì tôi muốn nói.
kuskmen

9
Ngoại trừ cho rằng đặt tên nó gạch dưới là khủng khiếp cũng
Icepickle

1
@AkashKava: câu hỏi không phải là liệu nó có phải là C # hợp pháp hay không, mà là liệu mã có dễ hiểu (và do đó dễ bảo trì và dễ đọc) khi được định dạng như thế này hay không. Sở thích cá nhân đóng một vai trò quan trọng, nhưng tôi có xu hướng đồng ý với Stuart.
PJTraill
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.