C #: как определить метод расширения как «с» в F #? - PullRequest
8 голосов
/ 26 июля 2011

F # имеет удобную функцию «с», пример:

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

F # создал ключевое слово «with», поскольку типы записей по умолчанию неизменны.

Теперь, возможно ли определить подобное расширение в C #? кажется, это немного сложно, так как в C # я не уверен, как преобразовать строку

Name="Test2"

делегату или выражению?

Ответы [ 4 ]

4 голосов
/ 26 июля 2011
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
}

Или, используя конструктор копирования:

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

И небольшое отклонение от превосходного предложения Джорджа, которое допускает несколько назначений:

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

Я бы, вероятно, использовалвторое, поскольку (1) любое решение общего назначения будет излишне медленным и запутанным;(2) он имеет наиболее близкий синтаксис к тому, что вы хотите (и синтаксис выполняет то, что вы ожидаете);(3) Выражения копирования и обновления F # реализованы аналогично.

2 голосов
/ 26 июля 2011

Может быть, что-то вроде этого:

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 голосов
/ 26 июля 2011

В C # нет собственной возможности сделать это, кроме метода расширения, но какой ценой? a и b являются ссылочными типами и любые предположения о том, что b основаны ( «с») на a вызывает немедленную путаницу относительно количества объектов, с которыми мы работаем. Есть только один? Является ли b копией a ? b указывают на a ?

C # не является F #.

Пожалуйста, смотрите предыдущий мой вопрос, на который ответил Эрик Липперт:

"Среди моих практических правил для написания понятного кода: помещать все побочные эффекты в операторы; выражения без операторов не должны иметь побочных эффектов. "

Более свободно C # / .NET

0 голосов
/ 26 июля 2011

В качестве альтернативы лямбда-функции вы можете использовать параметры со значениями по умолчанию. Единственная незначительная проблема заключается в том, что вам нужно выбрать какое-то значение по умолчанию, которое означает, что не изменяет этот параметр (для ссылочных типов), но null должен быть безопасным выбором:

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

Вы должны определить метод самостоятельно, но это всегда так (если вы не собираетесь использовать рефлексию, которая менее эффективна). Я думаю, что синтаксис также достаточно хорош. Если вы хотите сделать это так же хорошо, как в F #, то вам придется использовать F #: -)

...