2010-10-07 23 views
5

Così ho questo nel mio C# lib:parola chiave dinamica abilita "forse" monade?

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

Usato come:

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

tengo wracking mio cervello cercando di capire come utilizzare la parola chiave C# 4 dynamic per consentire questa sintassi invece :

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

ho avuto un paio di esempi che ho pensato lavorato ma hanno rotto verso il basso quando si avvia il concatenamento delle Maybe() s.

Qualche idea?

(Sto perdendo il mio tempo? È Maybe() una vittoria su IfNotNull())?

+2

forse ho avuto l'idea sbagliata, ma non sarebbe la ?? l'operatore è utile qui? – spender

+0

variabili dinamiche non possono vedere i metodi di estensione credo. –

+0

Personalmente mi piace molto il 'IfNotNull()' che hai attualmente. Dato che non puoi usare 'dynamic' con i metodi di estensione, la mia sensazione è che il codice potrebbe finire per essere orrendo. –

risposta

2

Non penso che usare il tipo dynamic sia una buona idea qui, perché la sintassi non sembrerà molto migliore e sacrificare il tipo safety (e IntelliSense) usando la digitazione dinamica.

Tuttavia, ecco un esempio di cosa è possibile fare. L'idea è di avvolgere gli oggetti in DynamicWrapper (il valore monadico :-)) che può contenere un valore null o un valore effettivo. Sarebbe ereditare da DynamicObject e delegare tutte le chiamate all'oggetto reale (se c'è) o tornare subito null (che sarebbe bind monadica):

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

L'esempio supporta solo le proprietà e utilizza riflessione in una bella modo inefficiente (penso che potrebbe essere ottimizzato usando la DLR). Ecco un esempio di come funziona:

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

Si noti che è possibile concatenare le chiamate liberamente - c'è solo qualcosa di speciale all'inizio (Wrap) e alla fine (Value), ma nel mezzo, si può scrivi .Another.Another.Another... quante volte vuoi.

1

Questa soluzione è simile a Tomas 'tranne che utilizza CallSite per richiamare le proprietà sull'istanza di destinazione e supporta anche il cast e chiamate extra a Maybe (come da esempio).

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

Il seguente codice dovrebbe dimostrare in uso:

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