2010-10-07 9 views
5

Więc mam to w moim C# lib:dynamiczne słowo kluczowe włącza "może" monadę?

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

używany jak:

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

Trzymam wracking mój mózg próbuje dowiedzieć się, jak używać słowa kluczowego C# 4 dynamic włączyć tę składnię zamiast :

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

miałem kilka przykładów, że myślałem pracowali ale zepsuł po uruchomieniu łańcuchowym Maybe() s.

Wszelkie pomysły?

(Czy tracę czas? Czy Maybe() wygrywa nad IfNotNull())?

+2

Może mam zły pomysł, ale czy nie ?? operator może być tutaj użyteczny? – spender

+0

Zmienne dynamiczne nie widzą metod rozszerzenia, jak sądzę. –

+0

Osobiście bardzo podoba mi się 'IfNotNull()', które obecnie masz. Ponieważ nie można używać 'dynamic' z metodami rozszerzeń, mam wrażenie, że kod może się okazać horrendalny. –

Odpowiedz

2

Nie sądzę, że używanie tutaj typu dynamic jest dobrym pomysłem, ponieważ składnia nie będzie wyglądać dużo lepiej i poświęcisz bezpieczeństwo typu (i IntelliSense) za pomocą dynamicznego pisania.

Poniżej znajduje się przykład tego, co możesz zrobić. Chodzi o to, że obiekty są zawijane w DynamicWrapper (twoja monadyczna wartość :-)), które mogą zawierać wartość null lub wartość rzeczywistą. Byłoby dziedziczą DynamicObject i przekazać wszystkie połączenia do rzeczywistego obiektu (jeśli istnieje) lub natychmiast powrócić null (to byłoby jednowartościowy wiązania):

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

Przykład obsługuje tylko właściwości i wykorzystuje odbicie w dość sposób nieefektywny (myślę, że można go zoptymalizować za pomocą DLR). Oto przykład jak to działa:

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

pamiętać, że można łańcucha wywołań swobodnie - nie jest tylko coś specjalnego na początku (Wrap) i na końcu (Value), ale w środku, można napisz .Another.Another.Another... tyle razy, ile chcesz.

1

To rozwiązanie jest podobne do narzędzia Tomas'a, z tym wyjątkiem, że używa ono CallSite do wywoływania właściwości w instancji docelowej, a także obsługuje odlewanie i dodatkowe wywołania do Maybe (jak w twoim przykładzie).

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

Poniższy kod należy wykazać je w użyciu:

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