Skip to content

Instantly share code, notes, and snippets.

@margusmartsepp
Last active May 24, 2016 15:30
Show Gist options
  • Save margusmartsepp/675fe83952bbf0b444cbfba0a53b9962 to your computer and use it in GitHub Desktop.
Save margusmartsepp/675fe83952bbf0b444cbfba0a53b9962 to your computer and use it in GitHub Desktop.
Generic Specification pattern
using System;
using System.Linq.Expressions;
namespace GenericSpecificationPattern
{
public interface ISpecification<T>
{
Expression<Func<T, bool>> Act { get; }
bool IsSatisfiedBy(T entity);
}
}
using System;
using System.Linq;
using System.Linq.Expressions;
namespace GenericSpecificationPattern
{
public class Specification<T> : ISpecification<T>
{
public static implicit operator Expression<Func<T, bool>>(Specification<T> specification)
{
return specification.Act;
}
public Expression<Func<T, bool>> Act { get; }
protected Specification(Expression<Func<T, bool>> expression)
{
Act = expression;
}
protected Specification(ISpecification<T> specification) : this(specification.Act)
{
}
public bool IsSatisfiedBy(T entity)
{
var query = new[] {entity}.AsQueryable();
return query.Any(Act);
}
public Specification<T> And(ISpecification<T> specification)
{
var andAlso = this.Binary<ISpecification<T>, T>(ExpressionType.AndAlso, specification);
return new Specification<T>(andAlso);
}
public Specification<T> Or(ISpecification<T> specification)
{
var orElse = this.Binary<ISpecification<T>, T>(ExpressionType.OrElse, specification);
return new Specification<T>(orElse);
}
public Specification<T> Not => new Specification<T>(this.Unary<ISpecification<T>, T>(ExpressionType.IsFalse));
}
}
using System;
using System.Linq;
using System.Linq.Expressions;
namespace GenericSpecificationPattern
{
public static class SpecificationExtensions
{
internal static Expression<Func<T, bool>> Unary<TS, T>(this TS thisSpecification, ExpressionType operatorExpression) where TS : ISpecification<T>
{
var expressionBody = Expression.MakeUnary(operatorExpression, thisSpecification.Act.Body, typeof(T));
var expressionLambda = Expression.Lambda<Func<T, bool>>(expressionBody, LeftParameters<TS, T>(thisSpecification));
return expressionLambda;
}
internal static Expression<Func<T, bool>> Binary<TS, T>(this TS thisSpecification, ExpressionType expressionType, TS thatSpecification) where TS : ISpecification<T>
{
var leftParameters = LeftParameters<TS, T>(thisSpecification);
var rightParameters = GetRightParameters<TS, T>(thatSpecification, leftParameters);
var expressionBody = Expression.MakeBinary(expressionType, thisSpecification.Act.Body, rightParameters);
var expressionLambda = Expression.Lambda<Func<T, bool>>(expressionBody, leftParameters);
return expressionLambda;
}
private static ParameterExpression LeftParameters<TS, T>(TS thisSpecification) where TS : ISpecification<T>
{
return thisSpecification.Act.Parameters.First();
}
private static Expression GetRightParameters<TS, T>(TS thatSpecification, ParameterExpression leftParameters) where TS : ISpecification<T>
{
return ReferenceEquals(leftParameters, thatSpecification.Act.Parameters.First())
? thatSpecification.Act.Body
: Expression.Invoke(thatSpecification.Act, leftParameters);
}
}
}
using System.Linq;
using System.Linq.Expressions;
using NUnit.Framework;
namespace GenericSpecificationPattern
{
[TestFixture]
public class SpecificationTests
{
private IQueryable<Movie> _movies;
private MpaaRatingExact _mpaaRatingG;
private MpaaRatingExact _mpaaRatingPg13;
private MpaaRatingExact _mpaaRatingR;
private MpaaRatingIsGreaterThan _mpaaRatingGreaterThanPg13;
[SetUp]
public void Init()
{
_movies = new[]
{
new Movie(MpaaRating.G),
new Movie(MpaaRating.Pg13),
new Movie(MpaaRating.R)
}.AsQueryable();
_mpaaRatingG = new MpaaRatingExact(MpaaRating.G);
_mpaaRatingPg13 = new MpaaRatingExact(MpaaRating.Pg13);
_mpaaRatingR = new MpaaRatingExact(MpaaRating.R);
_mpaaRatingGreaterThanPg13 = new MpaaRatingIsGreaterThan(MpaaRating.Pg13);
}
[Test]
public void FilterExample()
{
var isApredicate = _mpaaRatingG;
var isA = _movies.AsQueryable().Where(isApredicate).ToList();
Assert.That(isA.Count, Is.EqualTo(1));
Assert.That(isA.First().MpaaRating, Is.EqualTo(MpaaRating.G));
var isBpredicate = _mpaaRatingPg13;
var isB = _movies.AsQueryable().Where(isBpredicate).ToList();
Assert.That(isB.Count, Is.EqualTo(1));
Assert.That(isB.First().MpaaRating, Is.EqualTo(MpaaRating.Pg13));
var isCpredicate = _mpaaRatingR;
var isC = _movies.AsQueryable().Where(isCpredicate).ToList();
Assert.That(isC.Count, Is.EqualTo(1));
Assert.That(isC.First().MpaaRating, Is.EqualTo(MpaaRating.R));
}
[Test]
public void NotExample()
{
var isNotApredicate = _mpaaRatingG.Not;
var isNotA = _movies.Where(isNotApredicate).ToList();
Assert.That(isNotA.Count, Is.EqualTo(2));
}
[Test]
public void AndExample()
{
var isApredicate = _mpaaRatingG;
var isBpredicate = _mpaaRatingPg13;
var isAandBpredicate = isApredicate.And(isBpredicate);
var isAandB = _movies.Where(isAandBpredicate).ToList();
Assert.That(isAandB.Count, Is.EqualTo(0));
}
[Test]
public void OrExample()
{
var isAorBpredicate = _mpaaRatingPg13.Or(_mpaaRatingG);
var isAorB = _movies.AsQueryable().Where(isAorBpredicate).ToList();
Assert.That(isAorB.Count, Is.EqualTo(2));
}
[Test]
public void OrChainExample()
{
var isAorBorCpredicate = _mpaaRatingPg13.Or(_mpaaRatingG).Or(_mpaaRatingR);
var isAorBorC = _movies.AsQueryable().Where(isAorBorCpredicate).ToList();
Assert.That(isAorBorC.Count, Is.EqualTo(3));
}
[Test]
[TestCase(MpaaRating.Pg13, true)]
[TestCase(MpaaRating.R, false)]
public void IsSatisfiedByExample(MpaaRating mpaaRating, bool expectedResult)
{
var result = _mpaaRatingPg13.IsSatisfiedBy(new Movie(mpaaRating));
Assert.That(result, Is.EqualTo(expectedResult));
}
[Test]
public void OrChainMultiOriginExample()
{
var isGreaterThanBorBpredicate = _mpaaRatingGreaterThanPg13.Or(_mpaaRatingPg13);
var isGreaterThanBorB = _movies.AsQueryable().Where(isGreaterThanBorBpredicate).ToList();
Assert.That(isGreaterThanBorB.Count, Is.EqualTo(2));
}
}
public class MpaaRatingExact : Specification<Movie>
{
public MpaaRatingExact(MpaaRating rating)
: base(movie => movie.MpaaRating == rating) {}
}
public class MpaaRatingIsGreaterThan : Specification<Movie>
{
public MpaaRatingIsGreaterThan(MpaaRating rating)
: base(movie => movie.MpaaRating > rating) {}
}
public class Movie
{
public MpaaRating MpaaRating { get; }
public Movie(MpaaRating mpaaRating)
{
MpaaRating = mpaaRating;
}
}
public enum MpaaRating
{
G = 1,
Pg13 = 2,
R = 3
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment