所以我在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()
)?
答案 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);