Cascading Lambda Expression in C# - http://d-fens.ch/2017/03/05/nobrainer-cascading-lambda-expression-in-c/
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
namespace Net.Appclusive.Public.Types | |
{ | |
public abstract class Boxed : IConvertible | |
{ | |
public virtual TypeCode GetTypeCode() | |
{ | |
return TypeCode.Object; | |
} | |
public virtual bool ToBoolean(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual char ToChar(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual sbyte ToSByte(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual byte ToByte(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual short ToInt16(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual ushort ToUInt16(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual int ToInt32(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual uint ToUInt32(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual long ToInt64(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual ulong ToUInt64(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual float ToSingle(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual double ToDouble(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual decimal ToDecimal(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual DateTime ToDateTime(IFormatProvider provider) | |
{ | |
throw new NotImplementedException(); | |
} | |
public virtual string ToString(IFormatProvider provider) | |
{ | |
return null != provider | |
? string.Format(provider, base.ToString()) | |
: base.ToString(); | |
} | |
public abstract object ToType(Type conversionType, IFormatProvider provider); | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
using System.Diagnostics.Contracts; | |
using System.Linq.Expressions; | |
using System.Runtime.CompilerServices; | |
using BoxedType=System.Linq.Expressions.LambdaExpression; | |
namespace Net.Appclusive.Public.Types | |
{ | |
public sealed class BoxedLambdaExpression : Boxed<BoxedType> | |
{ | |
private const int PARAMETER_COUNT = 1; | |
private const int PARAMETER_INDEX = 0; | |
private const string PARAMETER_NAME = "$it"; | |
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |
private static BoxedType Combine(BoxedLambdaExpression expression1, BoxedType expression2, bool isOrElse) | |
{ | |
Contract.Assert(PARAMETER_COUNT == expression1?.Value.Parameters.Count); | |
Contract.Assert(PARAMETER_COUNT == expression2?.Parameters.Count); | |
Contract.Assert(expression1.Value.Parameters[PARAMETER_INDEX].Type == expression2.Parameters[PARAMETER_INDEX].Type); | |
var combinedExpression = isOrElse | |
? Expression.OrElse(expression1.Value.Body, expression2.Body) | |
: Expression.AndAlso(expression1.Value.Body, expression2.Body); | |
var visitor = new MemberExpressionVisitor(Expression.Parameter(expression1.Value.Parameters[PARAMETER_INDEX].Type, PARAMETER_NAME)); | |
var replacedExpression = visitor.Visit(combinedExpression); | |
var lambdaType = typeof(Func<,>).MakeGenericType(visitor.Parameter.Type, expression1.Value.ReturnType); | |
Contract.Assert(null != lambdaType); | |
var lambdaExpression = Expression.Lambda(lambdaType, replacedExpression, visitor.Parameter); | |
return lambdaExpression; | |
} | |
public static implicit operator BoxedType(BoxedLambdaExpression lambdaExpression) | |
{ | |
return lambdaExpression.Value; | |
} | |
public static implicit operator BoxedLambdaExpression(BoxedType boxedExpression) | |
{ | |
return new BoxedLambdaExpression | |
{ | |
Value = boxedExpression | |
}; | |
} | |
public static BoxedType operator +(BoxedLambdaExpression expression1, BoxedType expression2) | |
{ | |
return Combine(expression1, expression2, false); | |
} | |
public static BoxedType operator &(BoxedLambdaExpression expression1, BoxedType expression2) | |
{ | |
return Combine(expression1, expression2, false); | |
} | |
public static BoxedType operator |(BoxedLambdaExpression expression1, BoxedType expression2) | |
{ | |
return Combine(expression1, expression2, true); | |
} | |
public override object ToType(Type conversionType, IFormatProvider provider) | |
{ | |
// DFTODO - determine if and how we should implement a type conversion | |
throw new NotImplementedException(); | |
} | |
private class MemberExpressionVisitor : ExpressionVisitor | |
{ | |
public readonly ParameterExpression Parameter; | |
public MemberExpressionVisitor(ParameterExpression parameter) | |
{ | |
Contract.Requires(null != parameter); | |
Parameter = parameter; | |
} | |
protected override Expression VisitMember(MemberExpression node) | |
{ | |
return Expression.Property | |
( | |
Parameter, | |
node.Member.Name | |
); | |
} | |
} | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
using System; | |
using System.Linq.Expressions; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using Net.Appclusive.Public.Types; | |
namespace Net.Appclusive.Public.Tests.Types | |
{ | |
[TestClass] | |
public class BoxedLambdaExpressionTest | |
{ | |
public class SomeObject | |
{ | |
public long Id { get; set; } | |
public string Name { get; set; } | |
} | |
// ReSharper disable once InconsistentNaming | |
private static readonly Expression<Func<SomeObject, bool>> VALUE = e => e.Id != 0; | |
[TestMethod] | |
public void BoxingSucceeds() | |
{ | |
BoxedLambdaExpression boxedLambda = VALUE; | |
Assert.AreEqual(VALUE, boxedLambda.Value); | |
} | |
[TestMethod] | |
public void UnboxingSucceeds() | |
{ | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression unboxed = boxed; | |
Assert.AreEqual(VALUE, unboxed); | |
} | |
[TestMethod] | |
public void ImplicitCast1Succeeds() | |
{ | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression expression = boxed; | |
Assert.AreEqual(VALUE, expression); | |
} | |
[TestMethod] | |
public void ImplicitCast2Succeeds() | |
{ | |
var boxed = VALUE; | |
Expression expression = boxed; | |
Assert.AreEqual(VALUE, expression); | |
} | |
[TestMethod] | |
public void OperatorAddSucceeds() | |
{ | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression<Func<SomeObject, bool>> expression = e => !string.IsNullOrWhiteSpace(e.Name); | |
var result = boxed + expression; | |
var someObject = new SomeObject | |
{ | |
Id = 42, | |
Name = "tralala" | |
}; | |
var value = result.Compile().DynamicInvoke(someObject); | |
Assert.AreEqual(true, value); | |
} | |
[TestMethod] | |
public void OperatorAndSucceeds() | |
{ | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression<Func<SomeObject, bool>> expression = e => !string.IsNullOrWhiteSpace(e.Name); | |
var result = boxed & expression; | |
var someObject = new SomeObject | |
{ | |
Id = 0, | |
Name = "tralala" | |
}; | |
var value = result.Compile().DynamicInvoke(someObject); | |
Assert.AreEqual(false, value); | |
} | |
[TestMethod] | |
public void OperatorOrSucceeds() | |
{ | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression<Func<SomeObject, bool>> expression = e => !string.IsNullOrWhiteSpace(e.Name); | |
var result = boxed | expression; | |
var someObject = new SomeObject | |
{ | |
Id = 0, | |
Name = string.Empty | |
}; | |
var value = result.Compile().DynamicInvoke(someObject); | |
Assert.AreEqual(false, value); | |
} | |
[TestMethod] | |
public void ToString1Succeeds() | |
{ | |
var expected = @"$it => (($it.Id != 0) AndAlso Not(IsNullOrWhiteSpace($it.Name)))"; | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression<Func<SomeObject, bool>> expression = e => !string.IsNullOrWhiteSpace(e.Name); | |
var result = boxed + expression; | |
var value = result.ToString(); | |
Assert.AreEqual(expected, value); | |
} | |
[TestMethod] | |
public void ToString2Succeeds() | |
{ | |
var expected = @"$it => (($it.Id != 0) AndAlso Not(IsNullOrWhiteSpace($it.Name)))"; | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression<Func<SomeObject, bool>> expression = e => !string.IsNullOrWhiteSpace(e.Name); | |
var result = boxed & expression; | |
var value = result.ToString(); | |
Assert.AreEqual(expected, value); | |
} | |
[TestMethod] | |
public void ToString3Succeeds() | |
{ | |
var expected = @"$it => (($it.Id != 0) OrElse Not(IsNullOrWhiteSpace($it.Name)))"; | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
Expression<Func<SomeObject, bool>> expression = e => !string.IsNullOrWhiteSpace(e.Name); | |
var result = boxed | expression; | |
var value = result.ToString(); | |
Assert.AreEqual(expected, value); | |
} | |
[TestMethod] | |
[ExpectedException(typeof(NotImplementedException))] | |
public void ChangeTypeSucceeds() | |
{ | |
var boxed = new BoxedLambdaExpression | |
{ | |
Value = VALUE | |
}; | |
object valueAsObject = Convert.ChangeType(boxed, typeof(bool)); | |
} | |
} | |
} |
/** | |
* Copyright 2017 d-fens GmbH | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
namespace Net.Appclusive.Public.Types | |
{ | |
public abstract class Boxed<T> : Boxed | |
{ | |
public T Value { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment