2011-08-18 33 views
21

Tôi đang sử dụng một số công cụ chức năng trong C# và tiếp tục bị kẹt trên thực tế là List.Add không trả về danh sách được cập nhật.Có thành phần C# tương đương với toán tử dấu phẩy của C không?

Nói chung, tôi muốn gọi một hàm trên đối tượng và sau đó trả về đối tượng đã cập nhật.

Ví dụ nó sẽ là tuyệt vời nếu C# có một nhà điều hành dấu phẩy:

((accum, data) => accum.Add(data), accum) 

tôi có thể viết riêng "nhà điều hành dấu phẩy" của tôi như thế này:

static T comma(Action a, Func<T> result) { 
    a(); 
    return result(); 
} 

Có vẻ như nó sẽ làm việc nhưng trang web cuộc gọi sẽ xấu xí. Ví dụ đầu tiên của tôi sẽ giống như sau:

((accum, data) => comma(accum.Add(data),()=>accum)) 

Ví dụ đầy đủ! Cách sạch nhất để làm điều này mà không có một nhà phát triển khác đến sau và nhăn mũi của mình với mùi mã là gì?

+0

List.Add không trả lại một danh sách mới nhưng chỉ chỉnh sửa lại nó tại chỗ. Theo nghĩa này, nó không hoạt động. –

Trả lời

1

Bạn có thể thực hiện gần như chính xác ví dụ đầu tiên một cách tự nhiên bằng cách sử dụng các khối mã trong C# 3.0.

((accum, data) => { accum.Add(data); return accum; }) 
14

Tôi biết điều này là Fluent.

Một ví dụ thạo của một List.Add sử dụng phương pháp mở rộng

static List<T> MyAdd<T>(this List<T> list, T element) 
{ 
    list.Add(element); 
    return list; 
} 
+2

Cuộc gọi tốt bao gồm định nghĩa Thông thạo. –

+0

Nhưng tại sao bạn không chỉ sử dụng LINQ cho loại điều này? –

+0

@KevinRoche: Không ai cho rằng bạn không sử dụng LINQ. Trong thực tế, phương pháp này xuất hiện để tích hợp tốt với LINQ. – recursive

2

Phương pháp mở rộng được cho là giải pháp tốt nhất, nhưng cho đầy đủ vì lợi ích, đừng quên thay thế rõ ràng: một lớp wrapper.

public class FList<T> : List<T> 
{ 
    public new FList<T> Add(T item) 
    { 
     base.Add(item); 
     return this; 
    } 

    public new FList<T> RemoveAt(int index) 
    { 
     base.RemoveAt(index); 
     return this; 
    } 

    // etc... 
} 

{ 
    var list = new FList<string>(); 
    list.Add("foo").Add("remove me").Add("bar").RemoveAt(1); 
} 
2

Tôi nghĩ sẽ rất thú vị khi tạo phiên bản câu trả lời cho lớp bọc của tôi không yêu cầu bạn viết phương thức trình bao bọc.

public class FList<T> : List<T> 
{ 
    public FList<T> Do(string method, params object[] args) 
    { 
     var methodInfo = GetType().GetMethod(method); 

     if (methodInfo == null) 
      throw new InvalidOperationException("I have no " + method + " method."); 

     if (methodInfo.ReturnType != typeof(void)) 
      throw new InvalidOperationException("I'm only meant for void methods."); 

     methodInfo.Invoke(this, args); 
     return this; 
    } 
} 

{ 
    var list = new FList<string>(); 

    list.Do("Add", "foo") 
     .Do("Add", "remove me") 
     .Do("Add", "bar") 
     .Do("RemoveAt", 1) 
     .Do("Insert", 1, "replacement"); 

    foreach (var item in list) 
     Console.WriteLine(item);  
} 

Output:

foo 
replacement 
bar 

EDIT

Bạn canslim xuống cú pháp bằng cách khai thác C# thuộc tính lập chỉ mục.

Chỉ cần thêm phương pháp này:

public FList<T> this[string method, params object[] args] 
{ 
    get { return Do(method, args); } 
} 

Và gọi ngay bây giờ trông giống như:

list = list["Add", "foo"] 
      ["Add", "remove me"] 
      ["Add", "bar"] 
      ["RemoveAt", 1] 
      ["Insert", 1, "replacement"]; 

Với sự xuống hàng là không bắt buộc, tất nhiên.

Chỉ cần một chút vui nhộn khi hack cú pháp.

+2

Tôi chưa từng nghĩ đến việc sử dụng một thuộc tính index như thế này.Đồng thời tuyệt vời và xấu xí nhất;) (vì 2 lý do: thay đổi đối tượng trong thuộc tính get {} và sử dụng chuỗi phép thuật) – devio

+0

@devio, tôi sẽ không tranh chấp sự xấu xa (nhưng cũng rất tuyệt vời :)), nhưng đây không phải là dây ma thuật. Chuỗi ma thuật là thứ tạo ra kết quả đặc biệt/độc đáo. Nhưng sử dụng các chữ viết mã cứng như thế này tất nhiên bỏ qua việc kiểm tra thời gian biên dịch của cả hai tên phương thức (không có ký hiệu!) Và các kiểu. Nó kinda biến C# thành một ngôn ngữ năng động gõ hơi yếu (chưa kể đến địa ngục chậm!). Fun :) –

+1

Bạn có thể sử dụng một Enum cho các tên phương thức (chuyển đổi thành chuỗi khi chạy) - nó không tạo ra bất kỳ sự khác biệt nào về mặt kỹ thuật nhưng nó sẽ có nhiều lỗi hơn và ít bị lỗi hơn. (Hoặc bạn có thể sử dụng consts) –

3

Đây là nội dung Concat http://msdn.microsoft.com/en-us/library/vstudio/bb302894%28v=vs.100%29.aspx dành cho. Chỉ cần bọc một mục duy nhất trong một mảng. Mã chức năng không được thay đổi dữ liệu gốc. Nếu hiệu suất là một mối quan tâm, và điều này là không đủ tốt, sau đó bạn sẽ không còn được sử dụng các mô hình chức năng.

((accum, data) => accum.Concat(new[]{data})) 
+1

dường như LINQ-y-est của câu trả lời cho tôi. * nhún vai * – shelleybutterfly

2

Tôi biết rằng chủ đề này là rất cũ, nhưng tôi muốn thêm các thông tin sau cho người dùng trong tương lai:

Có hiện không phải là nhà điều hành như vậy. Trong chu kỳ phát triển C# 6 một semicolon operator đã được bổ sung, như:

int square = (int x = int.Parse(Console.ReadLine()); Console.WriteLine(x - 2); x * x); 

mà có thể được dịch như sau:

int square = compiler_generated_Function(); 

[MethodImpl(MethodImplOptions.AggressiveInlining)] 
private int compiler_generated_Function() 
{ 
    int x = int.Parse(Console.ReadLine()); 

    Console.WriteLine(x - 2); 

    return x * x; 
} 

Tuy nhiên, tính năng này đã bị bỏ trước khi trận chung kết C# phát hành.

+0

Tôi không thể biên dịch mẫu này trong Bản cập nhật VS2015 2, khung nhắm mục tiêu 4.6.1 và đặt rõ ràng phiên bản ngôn ngữ thành C# 6 không trợ giúp: Program.cs (9,27,9,30): lỗi CS1525: Cụm từ biểu thức không hợp lệ 'int' Program.cs (9,31,9,32): lỗi CS1026:) dự kiến ​​ Program.cs (9,31,9,32): lỗi CS1002:; dự kiến ​​ Program.cs (9,96,9,97): lỗi CS1002:; dự kiến ​​ Program.cs (9,96,9,97): lỗi CS1513:} được mong đợi Tôi đang làm gì sai? – ISanych

+2

Tính năng này đã bị loại bỏ khỏi C# 6. Nó không bao giờ có sẵn trong phiên bản phát hành. – Athari

0

Kỹ thuật khác, trực tiếp từ lập trình hàm, như sau. Xác định một struct IO như thế này:

/// <summary>TODO</summary> 
public struct IO<TSource> : IEquatable<IO<TSource>> { 
    /// <summary>Create a new instance of the class.</summary> 
    public IO(Func<TSource> functor) : this() { _functor = functor; } 

    /// <summary>Invokes the internal functor, returning the result.</summary> 
    public TSource Invoke() => (_functor | Default)(); 

    /// <summary>Returns true exactly when the contained functor is not null.</summary> 
    public bool HasValue => _functor != null; 

    X<Func<TSource>> _functor { get; } 

    static Func<TSource> Default => null; 
} 

và biến nó thành một đơn nguyên LINQ-thể với những phương pháp khuyến nông:

[SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")] 
public static class IO { 
    public static IO<TSource> ToIO<TSource>(this Func<TSource> source) { 
     source.ContractedNotNull(nameof(source)); 
     return new IO<TSource>(source); 
    } 

    public static IO<TResult> Select<TSource,TResult>(this IO<TSource> @this, 
     Func<TSource,TResult> projector 
    ) => 
     @this.HasValue && projector!=null 
      ? New(() => projector(@this.Invoke())) 
      : Null<TResult>(); 

    public static IO<TResult> SelectMany<TSource,TResult>(this IO<TSource> @this, 
     Func<TSource,IO<TResult>> selector 
    ) => 
     @this.HasValue && selector!=null 
      ? New(() => selector(@this.Invoke()).Invoke()) 
      : Null<TResult>(); 

    public static IO<TResult> SelectMany<TSource,T,TResult>(this IO<TSource> @this, 
     Func<TSource, IO<T>> selector, 
     Func<TSource,T,TResult> projector 
    ) => 
     @this.HasValue && selector!=null && projector!=null 
      ? New(() => { var s = @this.Invoke(); return projector(s, selector(s).Invoke()); }) 
      : Null<TResult>(); 

    public static IO<TResult> New<TResult> (Func<TResult> functor) => new IO<TResult>(functor); 

    private static IO<TResult> Null<TResult>() => new IO<TResult>(null); 
} 

và bây giờ bạn có thể sử dụng LINQ toàn diện cú pháp như sau:

using Xunit; 
[Fact] 
public static void IOTest() { 
    bool isExecuted1 = false; 
    bool isExecuted2 = false; 
    bool isExecuted3 = false; 
    bool isExecuted4 = false; 
    IO<int> one = new IO<int>(() => { isExecuted1 = true; return 1; }); 
    IO<int> two = new IO<int>(() => { isExecuted2 = true; return 2; }); 
    Func<int, IO<int>> addOne = x => { isExecuted3 = true; return (x + 1).ToIO(); }; 
    Func<int, Func<int, IO<int>>> add = x => y => { isExecuted4 = true; return (x + y).ToIO(); }; 

    var query1 = (from x in one 
        from y in two 
        from z in addOne(y) 
        from _ in "abc".ToIO() 
        let addOne2 = add(x) 
        select addOne2(z) 
       ); 
    Assert.False(isExecuted1); // Laziness. 
    Assert.False(isExecuted2); // Laziness. 
    Assert.False(isExecuted3); // Laziness. 
    Assert.False(isExecuted4); // Laziness. 
    int lhs = 1 + 2 + 1; 
    int rhs = query1.Invoke().Invoke(); 
    Assert.Equal(lhs, rhs); // Execution. 

    Assert.True(isExecuted1); 
    Assert.True(isExecuted2); 
    Assert.True(isExecuted3); 
    Assert.True(isExecuted4); 
} 

Khi người ta muốn một đơn nguyên IO soạn nhưng chỉ trả về void, hãy xác định cấu trúc này và phụ thuộc phương pháp ent:

public struct Unit : IEquatable<Unit>, IComparable<Unit> { 
    [CLSCompliant(false)] 
    public static Unit _ { get { return _this; } } static Unit _this = new Unit(); 
} 

public static IO<Unit> ConsoleWrite(object arg) => 
    ReturnIOUnit(() => Write(arg)); 

public static IO<Unit> ConsoleWriteLine(string value) => 
    ReturnIOUnit(() => WriteLine(value)); 

public static IO<ConsoleKeyInfo> ConsoleReadKey() => new IO<ConsoleKeyInfo>(() => ReadKey()); 

mà dễ dàng cho phép bằng văn bản về các đoạn mã như thế này:

from pass in Enumerable.Range(0, int.MaxValue) 
let counter = Readers.Counter(0) 
select (from state in gcdStartStates 
     where _predicate(pass, counter()) 
     select state) 
into enumerable 
where (from _ in Gcd.Run(enumerable.ToList()).ToIO() 
     from __ in ConsoleWrite(Prompt(mode)) 
     from c in ConsoleReadKey() 
     from ___ in ConsoleWriteLine() 
     select c.KeyChar.ToUpper() == 'Q' 
    ).Invoke() 
select 0; 

nơi cũ C dấu phẩy hành điểm dễ nhận ra cho những gì nó là: a monadic soạn hoạt động.

Các công đức thực sự của cú pháp hiểu rõ ràng khi một nỗ lực để viết rằng đoạn theo phong cách flunt:

(Enumerable.Range(0,int.MaxValue) 
      .Select(pass => new {pass, counter = Readers.Counter(0)}) 
      .Select(_ => gcdStartStates.Where(state => _predicate(_.pass,_.counter())) 
              .Select(state => state) 
        ) 
).Where(enumerable => 
    ((Gcd.Run(enumerable.ToList())).ToIO() 
     .SelectMany(_ => ConsoleWrite(Prompt(mode)),(_,__) => new {}) 
     .SelectMany(_ => ConsoleReadKey(),   (_, c) => new {c}) 
     .SelectMany(_ => ConsoleWriteLine(),  (_,__) => _.c.KeyChar.ToUpper() == 'Q') 
    ).Invoke() 
).Select(list => 0); 
Các vấn đề liên quan