2011-08-18 22 views
21

我在C#中使用了一些功能性的东西,并继续陷在List.Add未返回更新列表的事实中。是否有与C的逗号运算符相同的习惯C#?

一般来说,我想调用一个对象的函数,然后返回更新的对象。

例如这将是巨大的,如果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)) 

足够的例子!如果没有其他开发人员迟迟不会来,并且对代码味道皱起了眉头,干净的方法是什么?

+0

List.Add不会返回一个新的列表,但只是改变它的地方。从这个意义上讲,它不起作用。 –

回答

1

您几乎可以自然使用C#3.0中的代码块来完成第一个示例。

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

我知道这是Fluent

使用扩展方法

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

包含Fluent定义的良好调用。 –

+0

但是,为什么你不会只使用LINQ来处理这类事情呢? –

+0

@KevinRoche:没有人建议你不要使用linq。事实上,这种方法似乎与linq很好地结合在一起。 – recursive

2

扩展方法的List.Add的流利例子可以说是最好的解决方案,但为了完整起见,不要忘记明显替代:一个包装类。

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

我认为这将是一个有趣的做我的包装类答案,不需要你写封装方法的一个版本。

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"]; 

随着换行符是可选的,当然。

只是有点黑客的语法。

+2

我从来没有想过使用像这样的索引属性。在同一时间惊人和最丑陋的;)(2原因:改变对象在一个get {}财产,并使用魔术字符串) – devio

+0

@devio,我不会质疑丑(但它也有点酷:) ),但这些不是魔术字符串。魔术字符串是产生特殊/独特结果的东西。但是使用这样的硬编码文字当然会绕过编译时检查两个方法名称(无符号!)和类型。它有点变成一个smooshy弱类型的动态语言(更不用说地狱慢!)。 Fun :) –

+1

你可以使用Enum作为方法名(在运行时转换为字符串) - 它没有任何技术上的区别,但它会更整洁,更不容易出错。 (或者你可以使用常量) –

2

我知道这个线程是很老了,但我想追加以下信息,未来用户:

目前还不能这样操作。在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#发布之前丢弃。

+0

我无法在VS2015 Update 2中编译此示例,目标框架为4.6.1,并且明确将语言版本设置为C#6而不帮助:Program.cs(9,27,9,30):错误CS1525:无效的表达式项'int' Program.cs(9,31,9,32):error CS1026:)expected Program.cs(9,31,9,32):error CS1002:;预期 Program.cs(9,96,9,97):错误CS1002:;预计 Program.cs(9,96,9,97):错误CS1513:}预计 我做错了什么? – ISanych

+2

此功能从C#6中删除。它在发行版中从未提供。 – Athari

0

直接来自函数式编程的另一种技术如下。定义一个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,能够单子与这些扩展方法:

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

当需要一个组成IO返回值但只返回void时,定义该结构并依赖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()); 

这容易实现代码的片段这样写入:

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; 

在旧Ç逗号操作它是什么被容易地认识到:一个monadic 撰写操作。

的理解语法的真正好处是显而易见的,当一个人试图写在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); 
相关问题