2011-07-26 4 views
8

F 번호가 예 "와"편리한 기능이 있습니다C# : F #에서 "with"로 확장 메서드를 정의하는 방법?

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

F 번호 생성 키워드 "과"을 기록 유형은 불변 기본적으로되어있다.

이제 C#에서 비슷한 확장을 정의 할 수 있습니까? 내가 문자열 대리자 또는 식에

Name="Test2" 

를 변환하는 방법을 잘 모르겠어요은 C#에서와 같이 조금 까다로운 것 같다?

+0

나는 함수형 언어가 주류가 이러한 질문을 더 많이 볼 것으로 예상. 일반적인 자제는 "[내가 좋아하는 언어]에서 [[일부 기능]을 어떻게 할 수 있습니까?" – Daniel

답변

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 
} 

또는 복사 생성자 사용 :

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 # 복사 및 업데이트 표현식도 비슷하게 구현됩니다.

+0

할 수 있습니다 : var newFoo = foo.With (Bar = "boo-ya"); ? – athos

+0

@athos : 예, 다른 방법을 추가했습니다 (일반적인 목적은 아니지만 더 좋습니다). – Daniel

+0

@athos : 복사 생성자 제약 조건 (어쩌면'T : new (T)')이 있기를 바랄 것이므로 일반화 된 솔루션에 대해 반성 할 수 있습니다. 아마 불변성이 C#에서 더 나은 지원을 얻는 것처럼 우리는 이것을 쉽게 만드는 기능을 보게 될 것입니다. – Daniel

2
이 같은

아마 뭔가 :

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

같은 것을 할 수 있습니다 : var NewProduct = ExistingProduct.With (Name = "Test2"); ? – athos

+0

String.Split과 Reflection을 사용하면 다음과 같이 할 수 있습니다. var NewProduct = ExistingProduct.With ("Name = 'Test2'"); 그러나 컴파일 시간을 확인하지 않고 중요한 성능이 저하 될 수 있으므로 권장하지 않습니다. 람다 식은 당신이 얻는 것만 큼 멋집니다. – ForbesLindesay

+0

매우 읽기 쉽습니다. +1 – vlad

0

람다 함수 대신 매개 변수를 기본값으로 사용할 수 있습니다. 유일한 사소한 문제는 당신이 이 (참조 유형)이 매개 변수을 변경하지 않는 것을 의미 일부 기본값을 선택해야한다는 것입니다,하지만 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 # :-)을 사용해야합니다.

+1

동의합니다. 그냥 F #을 사용하십시오! – Daniel

+0

하하 토마스, 너도 알다시피, "F # 언어 리뷰"를 지금 읽고있어! 여기서 만나서 반갑습니다 :) – athos

+0

"With"는 제품 유형에 따라 다르지만 매개 변수를 기본값으로 사용하는 것은 현명하고 까다 롭습니다! :) – athos

0

C#에서 확장 메소드가 부족하지만 기본 비용은 얼마입니까? B 참조 유형과 우리가 노력하고 얼마나 많은 개체로 원인 즉시 혼란에 ("와") B의 기반이되는 어떤 제안입니다. 하나만 있나요? b 사본이 있습니까? 합니까 b을 가리키면 ?

C#이 F #이 아닙니다.

에릭 Lippert의 응답으로 내 이전 SO 질문을 참조하십시오

"명확한 코드를 작성하기위한 엄지 손가락의 제 규칙의 사이에있다 : 이 문에있는 모든 부작용을 넣어, 비 문장 표현은 어떤 측면이 없어야합니다 효과. "

More fluent C#/.NET

+0

안녕 Andleer, 에릭의 포인트는 항상 제 2의 생각에 합당합니다. 그냥 .. 만약 우리가 NUnit과 같은 현재의 TDD 프레임 워크를 확인한다면, 여러분의 질문과 비슷한 행이 있습니다 ... "자연어만큼 가까운 코드를 작성하십시오"라고 생각하는 방식으로 몇 가지 장점이 있습니다 ... 생각한다면 그렇지 않으면, 글쎄, 우리는 여전히 재미를위한 몇 가지 퀴즈 로이 질문을 취할 수 있습니다! : p – athos

+0

아토스, 개인적으로 나는 이것에 대해 강력하게 느끼지 않습니다. 전문가 수준에서 나는 동료들과 강한 논쟁을 할 수 있었기 때문에 에릭의 대답을 좋아했다. 이 일을 계속할 필요가 없습니다. 행운을 빕니다! – andleer

+0

@andleer - C#에서 F #의'with'와 비슷한 구조를 얻는 방법을 묻는 것이 공정한 질문이라고 생각합니다. 이것을 사용하면 _immutable_ 유형으로 작업하는 경우에만 의미가 있지만 C#에서는 완벽하게 유용한 방법입니다 (모든 값 유형은 불변이어야 함). 유형이 불변 인 경우 모든 우려 사항은 전혀 문제가되지 않습니다. 물론, 변경 가능한 유형을 사용하면 상황이 더욱 어려워집니다. 그러나 그 질문에 "F # record"를 볼 때 생각하는 것이 아닙니다 :-). –

관련 문제