2011-08-18 15 views
21

Sto utilizzando alcuni elementi funzionali in C# e continuo a rimanere bloccato sul fatto che List.Add non restituisce l'elenco aggiornato.Esiste un idioma C# equivalente all'operatore virgola di C?

In generale, mi piacerebbe chiamare una funzione su un oggetto e quindi restituire l'oggetto aggiornato.

Per esempio sarebbe bello se C# aveva un operatore virgola:

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

ho potuto scrivere il mio "operatore virgola" come questo:

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

Sembra che avrebbe funzionato, ma il sito di chiamata sarebbe brutto. Il mio primo esempio sarebbe qualcosa del tipo:

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

Basta esempi! Qual è il modo più pulito per farlo senza che un altro sviluppatore arrivi più tardi e arricciando il naso all'odore del codice?

+0

List.Add non restituisce una nuova lista, ma solo lo modifica in-place. In questo senso, non è funzionale. –

risposta

1

È possibile eseguire quasi esattamente il primo esempio in modo naturale utilizzando i blocchi di codice in C# 3.0.

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

Lo so come Fluent.

Un esempio perfetto di un List.Add utilizzando il metodo di estensione Metodi di estensione

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

Buona chiamata per includere la definizione Fluent. –

+0

Ma perché non dovresti usare LINQ per questo genere di cose? –

+0

@KevinRoche: nessuno suggerisce che non si usi linq. In effetti questo metodo sembra integrarsi bene con linq. – recursive

2

è senza dubbio la soluzione migliore, ma per completezza, non dimenticare l'ovvia alternativa: una classe 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

ho pensato che sarebbe interessante fare una versione del mio involucro risposta di classe che non richiede di scrivere i metodi wrapper.

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

uscita:

foo 
replacement 
bar 

EDIT

Si può dimagrire la sintassi sfruttando C# proprietà indicizzate.

Basta aggiungere questo metodo:

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

e la chiamata ora assomiglia a:

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

Con le interruzioni di riga facoltativa, naturalmente.

Solo un po 'di divertimento hackerando la sintassi.

+2

Non avrei mai pensato di usare una proprietà di indice come questa.Allo stesso tempo sorprendente e il più brutto;) (per 2 ragioni: cambiare l'oggetto in una proprietà get {}, e usando stringhe magiche) – devio

+0

@devio, non disputerò la bruttezza (ma è anche abbastanza bello :)), ma queste non sono stringhe magiche. Le stringhe magiche sono qualcosa che produce risultati speciali/unici. Ma usare letterali hardcoded come questo ovviamente aggira il controllo in fase di compilazione di entrambi i nomi dei metodi (senza simboli!) E dei tipi. Trasforma il C# in un linguaggio dinamico smooshy debolmente tipizzato (per non parlare dell'inferno lento!). Divertimento :) –

+1

Si potrebbe usare un Enum per i nomi dei metodi (convertito in stringa in fase di esecuzione) - non fa alcuna differenza tecnica ma sarebbe più ordinato e molto meno soggetto a errori. (O potevi usare le leggi) –

2

So che questa discussione è molto vecchio, ma voglio aggiungere le seguenti informazioni per i futuri utenti:

Non esiste attualmente un tale operatore. Durante il ciclo di sviluppo C# 6 un semicolon operator è stato aggiunto, come:

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

che può essere tradotto come segue:

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

Tuttavia, questa funzione era caduto prima del rilascio finale C#.

+0

Non riesco a compilare questo esempio nell'aggiornamento 2 VS2015, il framework di targeting 4.6.1 e l'impostazione esplicita della versione del linguaggio in C# 6 non aiuta: Program.cs (9,27,9,30): errore CS1525: termine espressione non valido 'int' Program.cs (9,31,9,32): errore CS1026:) previsto Program.cs (9,31,9,32): errore CS1002:; previsto Program.cs (9,96,9,97): errore CS1002:; previsto Program.cs (9,96,9,97): errore CS1513:} previsto Cosa sto facendo male? – ISanych

+2

Questa funzione è stata eliminata dal C# 6. Non è mai stata disponibile nella versione di rilascio. – Athari

0

Un'altra tecnica, direttamente dalla programmazione funzionale, è la seguente. Definire uno struct IO come questo:

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

e ne fanno una monade LINQ-in grado con questi metodi di estensione:

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

e ora è possibile utilizzare la LINQ sintassi completa così:

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

Quando si desidera un monade IO che compone ma restituisce solo vuoto, definire questa struttura e dipendere Metodi 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()); 

che consentono facilmente la scrittura di frammenti di codice come questo:

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; 

dove il vecchio C virgola operatore è facilmente riconosciuto per quello che è: un monadic composizione Operazione.

Il vero merito della sintassi di comprensione è evidente quando si tenta di scrivere quel frammento in stile 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); 
Problemi correlati