簡體   English   中英

是否有等同於C的逗號運算符的慣用C#?

[英]Is there idiomatic C# equivalent to C's comma operator?

我在C#中使用了一些功能性的東西,並不斷陷入List.Add不返回更新列表的事實。

通常,我想在對象上調用一個函數,然后返回更新后的對象。

例如,如果C#具有逗號運算符,那就太好了:

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

我可以這樣寫自己的“逗號運算符”:

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

看起來可以使用,但是呼叫站點很難使用。 我的第一個例子是:

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

足夠的例子! 沒有其他開發人員隨后出現並在代碼氣味中皺起鼻子的最干凈方法是什么?

我知道這是流利的

使用擴展方法添加列表的流利示例

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

我知道此線程非常舊,但是我想為以后的用戶添加以下信息:

當前沒有這樣的運算符。 在C#6開發周期中,添加了semicolon operator ,如下所示:

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

可以翻譯如下:

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

但是, 此功能在最終C#發行版之前已被刪除

這就是Concat http://msdn.microsoft.com/zh-cn/library/vstudio/bb302894%28v=vs.100%29.aspx的用途。 只需將單個項目包裝在數組中即可。 功能代碼不應更改原始數據。 如果性能是一個問題,而這還不夠好,那么您將不再使用功能范式。

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

您自然可以在C#3.0中使用代碼塊來完成幾乎第一個示例。

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

擴展方法可以說是最好的解決方案,但是出於完整性考慮,請不要忘記顯而易見的替代方法:包裝器類。

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

我認為制作一個不需要您編寫包裝器方法的包裝器類答案版本會很有趣。

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

輸出:

foo 
replacement 
bar

編輯

您可以通過利用C#索引屬性來簡化語法。

只需添加此方法:

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

現在,呼叫看起來像:

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

當然,換行符是可選的。

修改語法有點有趣。

直接來自函數編程的另一種技術如下。 定義這樣的IO結構:

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

並使用以下擴展方法使其成為LINQ可用的monad:

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

現在您可以使用LINQ 綜合語法,因此:

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

當需要組成但僅返回void的IO monad時,請定義此struct和相關方法:

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

可以輕松編寫如下代碼片段:

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;

在舊的C 逗號操作符是容易識別它是什么:一個單子合成操作。

當人們嘗試以直截了當的樣式編寫該片段時,理解語法的真正優點顯而易見:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM