2010-10-07 34 views
5

所以我有這個在我的C#的lib:動態關鍵字啓用「也許」monad?

public static TOut IfNotNull<TIn, TOut> 
    (this TIn instance, Func<TIn, TOut> func) 
{ 
    return instance == null ? default(TOut) : func(instance); 
} 

使用,如:

DateTime? expiration = promo.IfNotNull(p => p.TermsAndConditions.Expiration) 
          .IfNotNull(e => e.Date); 

我一直令人頭大我的大腦試圖找出如何使用C#4 dynamic關鍵字來啓用此語法代替:

DateTime? expiration = promoOffer.TermsAndConditions.Maybe() 
           .Expiration.Maybe() 
           .Date; 

我有,我認爲工作了幾個例子,但它們打破了,當你開始鏈接的Maybe()秒。

任何想法?

(我是在浪費我的時間嗎?是Maybe()勝過IfNotNull())?

+2

也許我已經得到了錯誤的想法,但不會在?操作員在這裏有用嗎? – spender 2010-10-07 00:41:27

+0

動態變量無法看到我相信的擴展方法。 – 2010-10-07 00:44:59

+0

我個人很喜歡'IfNotNull()'你現在有。由於你不能在擴展方法中使用'dynamic',所以我的感覺是代碼可能最終變得非常可怕。 – 2010-10-07 00:45:11

回答

2

我不認爲在這裏使用dynamic類型是一個好主意,因爲語法看起來不會更好,並且您使用動態類型來犧牲類型安全性(和智能感知)。

但是,下面是您可以做的一個示例。這個想法是,你將對象包裝成DynamicWrapper(你的monadic值:-)),它可以包含一個null值或一個實際值。它將從DynamicObject繼承和委託給實際對象的所有調用(如果有的話),或者立即返回null(這將是一元綁定):

class DynamicWrapper : DynamicObject { 
    public object Object { get; private set; } 
    public DynamicWrapper(object o) { Object = o; } 
    public override bool TryGetMember(GetMemberBinder binder, out object result) { 
    // Special case to be used at the end to get the actual value 
    if (binder.Name == "Value") result = Object; 
    // Binding on 'null' value - return 'null' 
    else if (Object == null) result = new DynamicWrapper(null); 
    else { 
     // Binding on some value - delegate to the underlying object 
     var getMeth = Object.GetType().GetProperty(binder.Name).GetGetMethod(); 
     result = new DynamicWrapper(getMeth.Invoke(Object, new object[0])); 
    return true; 
    } 
    public static dynamic Wrap(object o) { 
    return new DynamicWrapper(o); 
    } 
} 

的例子僅支持特性,它在一個漂亮的使用反射效率低下的方式(我認爲可以使用DLR進行優化)。這裏是如何工作的例子:

class Product { 
    public Product Another { get; set; } 
    public string Name { get; set; } 
} 

var p1 = new Product { Another = null }; 
var p2 = new Product { Another = new Product { Name = "Foo" } }; 
var p3 = (Product)null; 

// prints '' for p1 and p3 (null value) and 'Foo' for p2 (actual value) 
string name = DynamicWrapper.Wrap(p1).Another.Name.Value; 
Console.WriteLine(name); 

請注意,您可以自由鏈上的電話 - 只有一些特殊的開頭(Wrap),並在年底(Value),但在中間,你可以儘可能多地寫.Another.Another.Another...

1

該解決方案與Tomas'類似,不同之處在於它使用CallSite來調用目標實例的屬性,並且還支持對Maybe(根據您的示例)的投射和額外調用。

public static dynamic Maybe(this object target) 
{ 
    return new MaybeObject(target); 
} 

private class MaybeObject : DynamicObject 
{ 
    private readonly object _target; 

    public MaybeObject(object target) 
    { 
     _target = target; 
    } 

    public override bool TryGetMember(GetMemberBinder binder, 
             out object result) 
    { 
     result = _target != null ? Execute<object>(binder).Maybe() : this; 
     return true; 
    } 

    public override bool TryInvokeMember(InvokeMemberBinder binder, 
             object[] args, out object result) 
    { 
     if (binder.Name == "Maybe" && 
      binder.ReturnType == typeof (object) && 
      binder.CallInfo.ArgumentCount == 0) 
     { 
      // skip extra calls to Maybe 
      result = this; 
      return true; 
     } 

     return base.TryInvokeMember(binder, args, out result); 
    } 

    public override bool TryConvert(ConvertBinder binder, out object result) 
    { 
     if (_target != null) 
     { 
      // call Execute with an appropriate return type 
      result = GetType() 
       .GetMethod("Execute", BindingFlags.NonPublic | BindingFlags.Instance) 
       .MakeGenericMethod(binder.ReturnType) 
       .Invoke(this, new object[] {binder}); 
     } 
     else 
     { 
      result = null; 
     } 
     return true; 
    } 

    private object Execute<T>(CallSiteBinder binder) 
    { 
     var site = CallSite<Func<CallSite, object, T>>.Create(binder); 
     return site.Target(site, _target); 
    } 
} 

下面的代碼應證明其使用:

var promoOffer = new PromoOffer(); 
var expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date; 
Debug.Assert((DateTime?) expDate == null); 

promoOffer.TermsAndConditions = new TermsAndConditions(); 
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date; 
Debug.Assert((DateTime?) expDate == null); 

promoOffer.TermsAndConditions.Expiration = new Expiration(); 
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date; 
Debug.Assert((DateTime?) expDate == null); 

promoOffer.TermsAndConditions.Expiration.Date = DateTime.Now; 
expDate = promoOffer.TermsAndConditions.Maybe().Expiration.Maybe().Date; 
Debug.Assert((DateTime?) expDate != null);