2010-10-07 7 views
5

J'ai dans mon C# lib:mot-clé dynamique permet "peut-être" monad?

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

occasion comme:

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

Je garde wracking mon cerveau à essayer de comprendre comment utiliser le mot-clé C# 4 dynamic pour activer la syntaxe suivante :

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

J'ai eu des exemples de couple que je pensais, mais ils travaillé est tombé en panne lorsque vous démarrez enchaînant les Maybe() s.

Des idées?

(Est-ce que je perds mon temps? Est Maybe() une victoire sur IfNotNull())?

+2

Peut-être que j'ai une mauvaise idée, mais ne serait pas la ?? opérateur être utile ici? – spender

+0

les variables dynamiques ne peuvent pas voir les méthodes d'extension je crois. –

+0

Personnellement, j'aime bien le 'IfNotNull()' que vous avez actuellement. Comme vous ne pouvez pas utiliser 'dynamic' avec des méthodes d'extension, mon sentiment est que le code pourrait finir par être horrible. –

Répondre

2

Je ne pense pas qu'utiliser le type dynamic est une bonne idée ici, parce que la syntaxe ne va pas aller beaucoup mieux et que vous sacrifiez le type de sécurité (et IntelliSense) en utilisant le typage dynamique.

Cependant, voici un exemple de ce que vous pouvez faire. L'idée est que vous enveloppiez des objets dans DynamicWrapper (votre valeur monadic :-)) qui peut contenir une valeur null ou une valeur réelle. Il hériterait de DynamicObject et déléguer tous les appels à l'objet réel (s'il y en a) ou y retourner immédiatement null (ce serait bind monadique):

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'exemple prend en charge uniquement les propriétés et il utilise la réflexion dans un joli manière inefficace (je pense qu'il pourrait être optimisé en utilisant DLR). Voici un exemple comment cela fonctionne:

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

Notez que vous pouvez enchaîner les appels librement - il au début est que quelque chose de spécial (Wrap) et à la fin (Value), mais au milieu, vous pouvez écrivez .Another.Another.Another... autant de fois que vous le souhaitez.

1

Cette solution est similaire à celle de Tomas, sauf qu'elle utilise CallSite pour invoquer des propriétés sur l'instance cible et prend également en charge la diffusion et les appels supplémentaires vers Maybe (selon votre exemple).

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

Le code suivant doit le démontrer en cours d'utilisation:

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