C #: come definire un metodo di estensione come “con” in F #?
-
27-10-2019 - |
Domanda
F # ha una comoda funzione "con", ad esempio:
type Product = { Name:string; Price:int };;
let p = { Name="Test"; Price=42; };;
let p2 = { p with Name="Test2" };;
F # creato parola chiave "con" i tipi di record sono da immutabili impostazione predefinita.
Ora, è possibile definire una simile estensione in C #? sembra che sia un po 'complicato, come in C # Non sono sicuro di come convertire una stringa
Name="Test2"
a un delegato o di espressione?
Soluzione
public static T With<T, U>(this T obj, Expression<Func<T, U>> property, U value)
where T : ICloneable {
if (obj == null)
throw new ArgumentNullException("obj");
if (property == null)
throw new ArgumentNullException("property");
var memExpr = property.Body as MemberExpression;
if (memExpr == null || !(memExpr.Member is PropertyInfo))
throw new ArgumentException("Must refer to a property", "property");
var copy = (T)obj.Clone();
var propInfo = (PropertyInfo)memExpr.Member;
propInfo.SetValue(copy, value, null);
return copy;
}
public class Foo : ICloneable {
public int Id { get; set; }
public string Bar { get; set; }
object ICloneable.Clone() {
return new Foo { Id = this.Id, Bar = this.Bar };
}
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = foo.With(x => x.Bar, "boo-ya");
Console.WriteLine(newFoo.Bar); //boo-ya
}
In alternativa, utilizzando un costruttore di copia:
public class Foo {
public Foo(Foo other) {
this.Id = other.Id;
this.Bar = other.Bar;
}
public Foo() { }
public int Id { get; set; }
public string Bar { get; set; }
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = new Foo(foo) { Bar = "boo-ya" };
Console.WriteLine(newFoo.Bar);
}
E una leggera variazione su ottimo suggerimento di George, che permette di assegnazioni multiple:
public static T With<T>(this T obj, params Action<T>[] assignments)
where T : ICloneable {
if (obj == null)
throw new ArgumentNullException("obj");
if (assignments == null)
throw new ArgumentNullException("assignments");
var copy = (T)obj.Clone();
foreach (var a in assignments) {
a(copy);
}
return copy;
}
public static void Test() {
var foo = new Foo { Id = 1, Bar = "blah" };
var newFoo = foo.With(x => x.Id = 2, x => x.Bar = "boo-ya");
Console.WriteLine(newFoo.Bar);
}
Io probabilmente utilizzare il secondo dal momento che (1) ogni soluzione general purpose sta per essere inutilmente lento e contorto; (2) ha la sintassi più vicina a ciò che si vuole (e la sintassi fa quello che ci si aspetta); (3) F # espressioni di copia e aggiornamento vengono implementate in modo simile.
Altri suggerimenti
Forse qualcosa di simile:
void Main()
{
var NewProduct = ExistingProduct.With(P => P.Name = "Test2");
}
// Define other methods and classes here
public static class Extensions
{
public T With<T>(this T Instance, Action<T> Act) where T : ICloneable
{
var Result = Instance.Clone();
Act(Result);
return Result;
}
}
In alternativa alla funzione lambda, è possibile utilizzare i parametri con valori predefiniti. Il problema unico inconveniente è che devi scegliere un valore di default che significa non cambiano questo parametro (per i tipi di riferimento), ma null
dovrebbe essere una scelta sicura:
class Product {
public string Name { get; private set; }
public int Price { get; private set; }
public Product(string name, int price) {
Name = name; Price = price;
}
// Creates a new product using the current values and changing
// the values of the specified arguments to a new value
public Product With(string name = null, int? price = null) {
return new Product(name ?? Name, price ?? Price);
}
}
// Then you can write:
var prod2 = prod1.With(name = "New product");
È necessario definire il metodo da soli, ma questo è sempre il caso (a meno che non si sta andando a utilizzare la riflessione, che meno efficiente). Penso che la sintassi è ragionevolmente troppo bello. Se si vuole rendere il più bello come in F #, quindi dovrete usare F #: -)
Non v'è alcuna capacità nativa di fare questo in C # a corto di un metodo di estensione, ma a quale costo? a e b sono tipi di riferimento e qualsiasi suggerimento che b si basa ( "con") su a provoca confusione immediato da quanti oggetti stiamo lavorando con. c'è solo una? È b una copia di a ? Fa b punto a a
C # non è F #.
Si prega di consultare una precedente interrogazione SO mia come risposta di Eric Lippert:
"Tra le mie regole pratiche per la scrittura di codice chiaro è:. mettere tutti gli effetti collaterali nelle dichiarazioni; espressioni non-dichiarazione dovrebbe avere effetti collaterali "