2011-08-18 22 views
21

Estoy usando algunas funciones en C# y sigo atorado en el hecho de que List.Add no devuelve la lista actualizada.¿Hay un C# idiomático equivalente al operador de coma de C?

En general, me gustaría llamar a una función en un objeto y luego devolver el objeto actualizado.

Por ejemplo, sería grande si C# tenían un operador coma:

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

podría escribir mi propio "operador coma" como esto:

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

Parece que funcionaría, pero el sitio de llamada sería feo. Mi primer ejemplo sería algo así como:

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

¡Basta de ejemplos! ¿Cuál es la forma más limpia de hacer esto sin que otro desarrollador aparezca más adelante y arrugándose la nariz ante el olor del código?

+0

List.Add no devuelve una nueva lista, pero sólo lo modifica en el lugar. En este sentido, no es funcional. –

Respuesta

1

Puede hacer casi exactamente el primer ejemplo de forma natural usando bloques de código en C# 3.0.

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

Sé esto como Fluent.

Un ejemplo Fluido de un List.Add utilizando métodos de extensión

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

Buena llamada para incluir la definición Fluent. –

+0

¿Pero por qué no usarías LINQ para este tipo de cosas? –

+0

@KevinRoche: nadie sugiere que no uses linq. De hecho, este método parece integrarse bien con linq. – recursive

2

El método de extensión es sin duda la mejor solución, pero por el bien integridad, no se olvide la alternativa obvia: una clase contenedora.

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

Pensé que sería interesante hacer una versión de mi respuesta de la clase contenedora que no requiera que escriba los métodos de conteo.

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

Salida:

foo 
replacement 
bar 

EDITAR

Usted puede bajar de la sintaxis de C# explotando las propiedades indexadas.

sólo tiene que añadir este método:

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

Y la llamada ahora queda como:

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

Con los saltos de línea es opcional, por supuesto.

Solo un poco de diversión piratear la sintaxis.

+2

Nunca hubiera pensado en usar una propiedad de índice como esta.Al mismo tiempo increíble y más fea;) (por 2 razones: cambiar el objeto en una propiedad get {}, y usar cadenas mágicas) – devio

+0

@devio, no voy a disputar la fealdad (pero también es genial :)), pero estas no son cadenas mágicas. Las cadenas mágicas son algo que produce resultados especiales/únicos. Pero el uso de literales codificados como este por supuesto omite la verificación en tiempo de compilación de ambos nombres de método (¡sin símbolos!) Y tipos. Cuna convierte a C# en un lenguaje dinámico suave y terso (¡sin mencionar el infierno lento!). Fun :) –

+1

Puede usar un Enum para los nombres de los métodos (convertir a cadena en tiempo de ejecución) - no hace ninguna diferencia técnica, pero sería más nítido y mucho menos propenso a errores. (O podría usar consts) –

3

Esto es con lo que Concat http://msdn.microsoft.com/en-us/library/vstudio/bb302894%28v=vs.100%29.aspx es para. Simplemente envuelva un solo artículo en una matriz. El código funcional no debe mutar los datos originales. Si el rendimiento es una preocupación, y esto no es lo suficientemente bueno, entonces ya no usará el paradigma funcional.

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

me pareció el LINQ-y-est de las respuestas. * shrug * – shelleybutterfly

2

sé que este hilo es muy antiguo, pero quiero añadir la siguiente información para los futuros usuarios:

No existe actualmente un operador tal. Durante el ciclo de desarrollo C# 6, se añadió una semicolon operator, como:

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

que puede ser traducido como sigue:

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

Sin embargo, esta característica se dejó caer antes del lanzamiento final C#.

+0

No pude compilar esta muestra en VS2015 Update 2, targeting framework 4.6.1 y establecer explícitamente la versión de idioma a C# 6 no ayudando: Program.cs (9,27,9,30): error CS1525: término de expresión no válido 'int' Program.cs (9,31,9,32): error CS1026:) esperado Program.cs (9,31,9,32): error CS1002:; esperado Program.cs (9,96,9,97): error CS1002:; esperado Program.cs (9,96,9,97): error CS1513:} esperado ¿Qué estoy haciendo mal? – ISanych

+2

Esta característica se eliminó de C# 6. Nunca estuvo disponible en la versión de lanzamiento. – Athari

0

Otra técnica, directamente desde la programación funcional, es la siguiente. Definir una estructura IO así:

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

y que sea una mónada LINQ-poder con estos métodos de extensión:

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

y ahora se puede utilizar el LINQ sintaxis completa así:

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

Cuando se desea una món IO que compone pero solo devuelve void, defina esta estructura y dependa métodos 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()); 

que permiten fácilmente la escritura de fragmentos de código como esto:

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; 

donde el viejo C operador coma es fácilmente reconocida por lo que es: un monádico componer operación.

El verdadero mérito de la sintaxis de la comprensión es evidente cuando se intenta escribir ese fragmento en el estilo 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); 
Cuestiones relacionadas