动态关键字启用“也许”monad?

时间:2010-10-07 00:31:49

标签: c# dynamic

所以我在C#lib中有这个:

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 个答案:

答案 0 :(得分:2)

我不认为在这里使用dynamic类型是一个好主意,因为语法看起来不会更好,并且您通过使用动态类型来牺牲类型安全性(和IntelliSense)。

但是,这是一个你可以做的例子。这个想法是你将对象包装成DynamicWrapper(你的monadic值:-)),它可以包含null值或实际值。它将继承自DynamicObject并委托对实际对象的所有调用(如果有的话)或立即返回null(这将是monadic绑定):

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),但在中间,您可以编写{{1多少次你想要。

答案 1 :(得分: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);