2010-10-07 24 views
5

Así que tengo esto en mi C# lib:palabra clave dinámica habilita "tal vez" mónada?

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

utilizado como:

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

sigo arruina mi cerebro tratando de averiguar cómo utilizar el C# 4 dynamic palabra clave para permitir que esta sintaxis vez :

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

tuve un par de ejemplos que he opinión dieron pero se rompió cuando se inicia el encadenamiento de las Maybe() s.

¿Alguna idea?

(¿Estoy perdiendo el tiempo? ¿Es Maybe() una victoria sobre IfNotNull())?

+2

Tal vez tengo una idea equivocada, pero no lo haría el ?? operador ser de utilidad aquí? – spender

+0

variables dinámicas no pueden ver los métodos de extensión, creo. –

+0

Personalmente me gusta mucho el 'IfNotNull()' que tiene actualmente. Como no puedes usar 'dynamic' con los métodos de extensión, mi sensación es que el código podría terminar siendo horrendo. –

Respuesta

2

No creo que el uso del tipo dynamic sea una buena idea aquí, porque la sintaxis no se verá mucho mejor y sacrifica la seguridad del tipo (e IntelliSense) mediante el uso de tipeo dinámico.

Sin embargo, aquí hay un ejemplo de lo que puede hacer. La idea es que envuelva objetos en DynamicWrapper (su valor monádico :-)) que puede contener un valor null o un valor real. Sería hereda de DynamicObject y delegar todas las llamadas al objeto real (si hay alguna) o inmediatamente volver null (que sería unen monádico):

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

El ejemplo sólo es compatible con las propiedades y se utiliza la reflexión en un bonito forma ineficiente (creo que podría optimizarse usando DLR). Aquí está un ejemplo de cómo funciona:

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

Tenga en cuenta que se pueden encadenar las llamadas libremente - sólo hay algo especial al principio (Wrap) y al final (Value), pero en el medio, puede escriba .Another.Another.Another... tantas veces como desee.

1

Esta solución es similar a la de Tomás, excepto que usa CallSite para invocar propiedades en la instancia de destino y también admite el casting y llamadas adicionales a Maybe (según su ejemplo).

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

El siguiente código debe demostrar que está en 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); 
Cuestiones relacionadas