Question

F # a une fonction pratique "avec", par exemple:

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

F # créé mot-clé "avec" les types d'enregistrements sont par immuable par défaut.

Maintenant, est-il possible de définir une extension similaire en C #? semble qu'il est un peu difficile, comme en C # je ne suis pas sûr de savoir comment convertir une chaîne

Name="Test2"

à un délégué ou d'expression?

Était-ce utile?

La solution

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
}

Ou, en utilisant un constructeur de copie:

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

Et une légère variation sur une excellente suggestion de George, qui permet de multiples tâches:

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

J'utiliserait probablement le second depuis (1) toute solution à usage général va être inutilement lent et alambiqué; (2) il a la syntaxe plus proche de ce que vous voulez (et la syntaxe fait ce que vous attendez); (3) F # et expressions copie de mise à jour sont mises en œuvre de façon similaire.

Autres conseils

Peut-être quelque chose comme ceci:

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

Comme une alternative à la fonction lambda, vous pouvez utiliser des paramètres avec des valeurs par défaut. Le seul problème mineur est que vous devez choisir une valeur par défaut des moyens ne changent pas ce paramètre (pour les types de référence), mais null doit être un choix sûr:

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

Vous devez définir la méthode vous-même, mais qui est toujours le cas (sauf si vous allez à la réflexion de l'utilisation, ce qui moins efficace). Je pense que la syntaxe est assez bien aussi. Si vous voulez le rendre aussi agréable que dans F #, alors vous devrez utiliser F #: -)

Il n'y a pas de capacité native de le faire en C # à court d'une méthode d'extension, mais à quel prix? a et b sont les types de référence et toute suggestion que b est basée ( "avec") sur a provoque la confusion immédiate pour le nombre d'objets que nous travaillons avec. Est-il un seul? b une copie de a ? Est-ce que b point a

C # est pas F #.

S'il vous plaît voir une question précédente SO de mine répondu par Eric Lippert:

« Parmi mes règles de base pour écrire du code clair. mettre tous les effets secondaires dans les états, les expressions non-déclaration ne devrait pas avoir d'effets secondaires »

Plus couramment C # / .NET

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top