Предположим, у меня есть выражение, представляющее собой просто цепочку операторов доступа к членам:
Expression> e = x => x.foo.bar.baz;
Вы можете думать об этом выражении как о композиции подвыражения, каждое из которых включает одну операцию доступа к членам:
Expression> e1 = (Tx x) => x.foo;
Expression> e2 = (Tfoo foo) => foo.bar;
Expression> e3 = (Tbar bar) => bar.baz;
Что я хочу сделать, так это разбить e
на эти составные подвыражения, чтобы я мог работать с ними по отдельности.
Если у меня есть выражение x => x.foo.bar
, я уже знаю, как прервать x => x.foo
. Как я могу получить другое подвыражение, foo => foo.bar
?
Я пытаюсь имитировать «поднятие» оператора доступа к членам в C#, например оператор экзистенциального доступа CoffeeScript ?.
. Эрик Липперт заявил, что аналогичный оператор рассматривался для C#,но не было бюджета для его реализации.
Если бы такой оператор существовал в C#, вы могли бы сделать что-то вроде этого:
value = target?.foo?.bar?.baz;
Если какая-то часть цепочки target.foo.bar.baz
оказывалась нулевой, то вся эта штука будет оцениваться как null, что позволит избежать исключения NullReferenceException.
Мне нужен метод расширения Lift
, который может имитировать такие вещи:
value = target.Lift(x => x.foo.bar.baz); //returns target.foo.bar.baz or null
У меня есть нечто, что компилируется, и вроде как работает.Однако он неполный, поскольку я знаю только, как сохранить левую часть выражения доступа к члену. Я могу превратить x => x.foo.bar.baz
в x => x.foo.bar
, но не знаю, как сохранить bar => бар.баз
.
Таким образом, получается что-то вроде этого (псевдокод):
return (x => x)(target) == null ? null
: (x => x.foo)(target) == null ? null
: (x => x.foo.bar)(target) == null ? null
: (x => x.foo.bar.baz)(target);
Это означает, что крайние левые шаги в выражении вычисляются снова и снова. Может быть, это не имеет большого значения, если они являются просто свойствами объектов POCO, но превратите их в вызовы методов, и неэффективность (и потенциальные побочные эффекты) станут намного более очевидными:
//still pseudocode
return (x => x())(target) == null ? null
: (x => x().foo())(target) == null ? null
: (x => x().foo().bar())(target) == null ? null
: (x => x().foo().bar().baz())(target);
static TResult Lift(this T target, Expression> exp)
where TResult : class
{
//omitted: if target can be null && target == null, just return null
var memberExpression = exp.Body as MemberExpression;
if (memberExpression != null)
{
//if memberExpression is {x.foo.bar}, then innerExpression is {x.foo}
var innerExpression = memberExpression.Expression;
var innerLambda = Expression.Lambda>(
innerExpression,
exp.Parameters
);
if (target.Lift(innerLambda) == null)
{
return null;
}
else
{
////This is the part I'm stuck on. Possible pseudocode:
//var member = memberExpression.Member;
//return GetValueOfMember(target.Lift(innerLambda), member);
}
}
//For now, I'm stuck with this:
return exp.Compile()(target);
}
Это было частично вдохновлено этот ответ.
value = x.ToMaybe()
.Bind(y => y.foo)
.Bind(f => f.bar)
.Bind(b => b.baz)
.Value;
Плюсы:
Минусы:
SelectMany
и использую синтаксис запроса, ИМХО, это будет выглядеть более грязно, а не менее.x.foo.bar.baz
как его отдельные компоненты, что означает, что я должен знать, что они из себя представляют, во время компиляции. Я не могу просто использовать выражение из переменной типа result = Lift(expr, obj);
.Я модифицировал метод LiftMemberAccessToNull Яна Гриффита в универсальный метод расширения, который можно использовать так, как я описал. Код слишком длинный, чтобы включать его сюда, но я опубликую Gist, если кому-то интересно.
Плюсы:
result = target.Lift(x => x.foo.bar.baz)
Минусы:
Nullable
.try
{
value = x.foo.bar.baz;
}
catch (NullReferenceException ex)
{
value = null;
}
Это наиболее очевидный способ, и я воспользуюсь им, если не найду более элегантного способа.
Плюсы:
Минусы:
Не буду врать; «Непризнание поражения» — главная причина моего упрямства. Мои инстинкты подсказывали, что должен быть элегантный способ сделать это, но найти его было непросто. Не могу поверить, что так легко получить доступ к левой части выражения, а правая почти недостижима.
У меня действительно есть две проблемы, поэтому я соглашусь со всем, что решает любую из них:
Доступ к элементам с распространением NULL запланирован длявключения в C# 6.0. Тем не менее, мне все еще нужно решение для декомпозиции выражений.