2011-07-26 16 views
8

F # tiene una función muy cómoda "con", ejemplo:C#: ¿cómo definir un método de extensión como "con" en F #?

type Product = { Name:string; Price:int };; 
let p = { Name="Test"; Price=42; };; 
let p2 = { p with Name="Test2" };; 

F # palabra clave creada "con", como los tipos de registro por defecto son inmutables.

Ahora, ¿es posible definir una extensión similar en C#? parece que es un poco complicado, ya que en C# no estoy seguro de cómo convertir una cadena

Name="Test2" 

a un delegado o expresión?

+0

que esperan ver más de estas preguntas como lenguajes funcionales convertido en algo corriente. Un estribillo común será "¿Cómo puedo hacer [alguna característica funcional] en [mi idioma favorito]?" – Daniel

Respuesta

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

O, usando un constructor de 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); 
} 

Y una ligera variación en excelente sugerencia de George, que permite múltiples tareas:

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

Probablemente usaría el segundo, ya que (1) cualquier solución de uso general será innecesariamente lenta y complicada; (2) tiene la sintaxis más cercana a lo que desea (y la sintaxis hace lo que espera); (3) Las expresiones F # copy-and-update se implementan de manera similar.

+0

es posible hacer: var newFoo = foo.With (Bar = "boo-ya"); ? – athos

+0

@athos: Sí, agregué otro método (mejor, aunque no de propósito general). – Daniel

+0

@athos: Ojalá hubiera una restricción de constructor de copia (tal vez 'donde T: nuevo (T)') para que pudiéramos reflexionar sobre soluciones generalizadas a esto. Quizás a medida que la inmutabilidad gane un mejor soporte en C#, veremos características que lo hacen más fácil. – Daniel

2

Tal vez algo como esto:

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

es posible hacer algo como: var NewProduct = ExistingProduct.With (Name = "Test2"); ? – athos

+0

Es poco probable que pueda, usando String.Split y Reflection, hacer algo como: var NewProduct = ExistingProduct.With ("Name = 'Test2'"); Pero luego no obtiene el tiempo de compilación y toma un golpe de rendimiento significativo, así que no lo recomendaría. Una expresión lambda es tan agradable como es probable que consigas. – ForbesLindesay

+0

muy legible. +1 – vlad

0

Como alternativa a la función lambda, puede usar parámetros con valores predeterminados. El único problema es que usted tiene que escoger un valor por defecto que significa no cambia este parámetro (para los tipos de referencia), pero null debe ser una opción segura:

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

Usted tiene que definir el método mismo , pero ese es siempre el caso (a menos que vayas a utilizar el reflejo, que es menos eficiente). Creo que la sintaxis es razonablemente buena también. Si quieres hacerlo tan bonito como en F #, entonces tendrás que usar F # :-)

+1

Estoy de acuerdo, solo use F #! – Daniel

+0

jaja Tomás, ¿sabes qué? ¡Estoy leyendo tu "revisión del lenguaje F #" ahora mismo! Me alegro de verte aquí :) – athos

+0

Aunque este "Con" depende del tipo de producto, el uso de parámetros con valores predeterminados es un truco inteligente e interesante! :) – athos

0

No hay una habilidad nativa para hacer esto en C# salvo un método de extensión, pero ¿a qué costo? un y b son los tipos de referencia y cualquier sugerencia de que b se basa ("con") en un causas confusión inmediata en cuanto a la cantidad de objetos que estamos trabajando. ¿Hay solo uno? Es b una copia de a? ¿Tiene b punto a a?

C# no es F #.

Por favor, vea una pregunta anterior, por lo mío como respondida por Eric Lippert:

"Entre mis reglas de oro para escribir código claro es: poner todos los efectos secundarios en los estados; expresiones no de los estados no deben tener ningún lado efectos. "

More fluent C#/.NET

+0

hola Andleer, los puntos de Eric son siempre dignos de un segundo pensamiento. simplemente ... si revisamos los frameworks actuales de TDD como NUnit, hay líneas similares a tu pregunta y a la mía ... de alguna manera creo que "escribir código tan cercano como el inglés natural" tiene algunos méritos allí ... si piensas de lo contrario, bueno, todavía podemos tomar esta pregunta como un cuestionario por diversión. : p – athos

+0

Athos, a nivel personal, no me siento muy convencido de esto. A nivel profesional, me gustó la respuesta de Eric porque me permitió hacer una fuerte discusión con mis compañeros de trabajo. No hay necesidad de seguir dando vueltas en esto. ¡Buena suerte! – andleer

+0

@andleer - Creo que es una buena pregunta para preguntar cómo obtener algún constructo similar al 'con' de F # en C#. Usar esto solo tiene sentido si está trabajando con tipos _immutable_, pero ese es un enfoque perfectamente útil en C# (después de todo, todos los tipos de valores deben ser inmutables). Si el tipo es inmutable, todas sus inquietudes no son un problema en absoluto. Por supuesto, al usar el tipo mutable, las cosas se vuelven más difíciles, pero eso no es lo que pienso cuando veo "F # record" en la pregunta :-). –

Cuestiones relacionadas