I have a following design in Java (7) application:
There is a method where i pass collection of objects of some type and object that I call "predicate" that is used to filter given collection.
Predicate is an interface with one method called test - it takes an object and return a boolean value.
In such situation:
Now I would like to have similar design in C# (4.0) application. I can see two way of doing it - by mimic Java design, or by change Predicate into delegate:
I can try to mimic Java design, but:
I can try achieve similar design using delegates. My filter method will take collection of object and Predicate delegate. In such case:
My question is - what in your opinion will be best way of achieve the same (or as similar as possible) design that I have in Java, that will be considered as correct, clean C#-approach of doing that?
I have few years of experience in programming in Java, but less that a year in C#, so I understand that maybe some problems that I see just doesn't exist in C# world or are not considered as problems at all.
EDIT : here is simplest (I think...) possible example of how my Java code work:
My "domain" object:
public class Person {
private final String firstName;
private final String secondName;
public Person(String firstName, String secondName) {
this.firstName = firstName;
this.secondName = secondName;
}
public String getFirstName() {
return firstName;
}
public String getSecondName() {
return secondName;
}
}
Filtering class:
public class Filter {
public Collection<Person> filter(Collection<Person> collection, Predicate predicate) {
Collection<Person> result = new LinkedList<Person>();
for(Person person: collection) {
if(predicate.test(person)) {
result.add(person);
}
}
return result;
}
}
Predicate interface:
public interface Predicate {
boolean test(Person person);
}
Two simple predefined implementations:
public class FirstNameStartsWithPredicate implements Predicate {
private final String startsWith;
public FirstNameStartsWithPredicate(String startsWith) {
this.startsWith = startsWith;
}
public boolean test(Person person) {
return person.getFirstName().startsWith(startsWith);
}
}
public class LastNameEndsWithPredicate implements Predicate {
private final String endsWith;
public LastNameEndsWithPredicate(String endsWith) {
this.endsWith = endsWith;
}
public boolean test(Person person) {
return person.getSecondName().endsWith(endsWith);
}
}
Utility class:
public final class PredicateUtils {
public static Predicate and(final Predicate first, final Predicate second) {
return new Predicate() {
public boolean test(Person person) {
return first.test(person) && second.test(person);
}
};
}
public static Predicate or(final Predicate first, final Predicate second) {
return new Predicate() {
public boolean test(Person person) {
return first.test(person) || second.test(person);
}
};
}
public static Predicate allwaysTrue() {
return new Predicate() {
public boolean test(Person person) {
return true;
}
};
}
}
And finally, example of usage:
Collection<Person> persons = Arrays.asList(
new Person("John", "Done"),
new Person("Jane", "Done"),
new Person("Adam", "Smith")
);
Filter filter = new Filter();
// Predefined predicates
filter.filter(persons, new FirstNameStartsWithPredicate("J"));
filter.filter(persons, new LastNameEndsWithPredicate("e"));
// anonymous implementation
filter.filter(persons, new Predicate() {
public boolean test(Person person) {
return person.getFirstName().equals("Adam") && person.getSecondName().equals("Smith");
}
});
// utility class
filter.filter(persons, PredicateUtils.allwaysTrue());
filter.filter(persons, PredicateUtils.and(new FirstNameStartsWithPredicate("J"), new LastNameEndsWithPredicate("e")));
filter.filter(persons, PredicateUtils.or(new FirstNameStartsWithPredicate("J"), new FirstNameStartsWithPredicate("A")));
This is totally doable in C#. Let's say we have a few users, and filters to filter them, some filters are built-in and some are implemented by users. Here we should use interfaces like Java instead of delegates.
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
public interface IPredicate<T>
{
bool IsValid(T entity);
}
public class UserPredicate : IPredicate<User>
{
/* built-in predicates */
public static UserPredicate Adult = new UserPredicate(u => u.Age >= 18);
public static UserPredicate NoAddress = new UserPredicate(u => string.IsNullOrEmpty(u.Address));
public Func<User, bool> Predicate { get; private set; }
public UserPredicate(Func<User, bool> predicate)
{
this.Predicate = predicate;
}
bool IPredicate<User>.IsValid(User entity)
{
return this.Predicate(entity);
}
}
Users can easily add new predicates like this:
//user's code
var custom = new UserPredicate(MyCustomUserFilter);
bool MyCustomUserFilter(User u)
{
//user's filter logic
}
It's not the same as Java, because in C# anonymous types can't implement an interface.
And also it's very easy to "combine" the predicates into a new one.
var AdultWithNoAddress = new UserPredicate(u => UserPredicate.Adult.Predicate(u)
&& UserPredicate.NoAddress.Predicate(u));
EDIT To make the combination of predicates more clearer, you can put the combination logic into predicates itself.
public interface IPredicate<T>
{
bool IsValid(T entity);
IPredicate<T> And(IPredicate<T> another);
IPredicate<T> Or(IPredicate<T> another);
}
public class UserPredicate : IPredicate<User>
{
public static UserPredicate Adult = new UserPredicate(u => u.Age >= 18);
public static UserPredicate NoAddress = new UserPredicate(u => string.IsNullOrEmpty(u.Address));
private Func<User, bool> _predicate;
public UserPredicate(Func<User, bool> predicate)
{
_predicate = predicate;
}
public bool IsValid(User entity)
{
return _predicate(entity);
}
public IPredicate<User> And(IPredicate<User> another)
{
return new UserPredicate(u => this.IsValid(u) && another.IsValid(u));
}
public IPredicate<User> Or(IPredicate<User> another)
{
return new UserPredicate(u => this.IsValid(u) || another.IsValid(u));
}
}
//usage
var AdultWithNoAddress = UserPredicate.Adult.And(UserPredicate.NoAddress);
I do not think, that implementing this would be necessary, because you could just use the Linq method, that is a given in C#
Where(Func<T, bool> precicate)
You can define predicates on the fly using lambda notation
persons.Where(p => p.Adresses.None() && p.Age == 15);
You can easily combine predefined predicates by using && or ||.
So you can write
persons.Where(p => MyPredicates.HasAddress(p) && MyPredicates.IsAdult(p))
EDIT: Here is the link to MSDN: https://msdn.microsoft.com/en-us/library/bb534803(v=vs.100).aspx
Okay, Fiddle here.
This first snippet pretty much takes care of applying a sequence of predicates to a sequence of values.
using System.Linq;
...
public class Person
{
public string FirstName { get; }
public string LastName { get; }
}
....
IEnumerable<Person> people = ...
var predicatesA = new Predicate<Person>[]
{
p => p.FirstName.StartsWith("J"),
p => p.LastName.EndsWith("e"),
};
var predicatesB = new Predicate<Person>[]
{
p => p.FirstName.StartsWith("J"),
p => p.FirstName.StartsWith("A"),
};
var peopleWithBothOfA = people.Where(p => predicatesA.All(c => c(p)));
var peopleWithEitherOfB = people.Where(p => predicatesB.Any(c => c(p)));
what C# doesn't have, as far as I'm aware, is an inline way to instantiate the predicate sequence once and apply it to another sequence.
Your question got me interested so I put the effort in to come up with a solution,
using System;
using System.Collections.Generic;
using System.Linq;
public class Program
{
public static void Main()
{
var people = new Person[0];
var filtered = people.Apply(
BooleanConnective.AndAlso,
p => p.FirstName.StartsWith("J"),
p => p.FirstName.StartsWith("A"));
}
}
public class Person
{
public string FirstName { get; }
public string LastName { get; }
}
here is the code with a Fiddle , that makes it work. Not fully tested.
public enum BooleanGreed
{
Greedy = 0,
LazyFalse,
LazyTrue
}
public interface IBooleanConnective
{
bool Operator(bool left, bool right);
BooleanGreed Greed { get; }
}
public sealed class BooleanConnective : IBooleanConnective
{
private BooleanConnective(
Func<bool, bool, bool> @operator,
BooleanGreed greed)
{
this.OperatorFunction = @operator;
this.Greed = greed;
}
public static IBooleanConnective AndAlso { get; } =
new BooleanConnective(
(left, right) => left && right,
BooleanGreed.LazyFalse);
public static IBooleanConnective OrElse { get; } =
new BooleanConnective(
(left, right) => left || right,
BooleanGreed.LazyTrue);
public BooleanGreed Greed { get; }
private Func<bool, bool, bool> OperatorFunction { get; }
public bool Operator(bool left, bool right)
{
return this.OperatorFunction(left, right);
}
}
public static class Ext
{
public static IEnumerable<T> Apply<T>(
this IEnumerable<T> source,
IBooleanConnective connective,
params Predicate<T>[] predicates)
{
return source.Where(t => MergePredicates(t, predicates, connective));
}
private static bool MergePredicates<T>(
T t,
IEnumerable<Predicate<T>> predicates,
IBooleanConnective connective)
{
var e = predicates.GetEnumerator();
if (!e.MoveNext())
{
return false;
}
var value = e.Current(t);
switch (connective.Greed)
{
case BooleanGreed.Greedy:
break;
case BooleanGreed.LazyFalse:
if (!value)
{
return false;
}
break;
default:
if (value)
{
return true;
}
break;
}
while (e.MoveNext())
{
value = connective.Operator(value, e.Current(t));
switch (connective.Greed)
{
case BooleanGreed.Greedy:
break;
case BooleanGreed.LazyFalse:
if (!value)
{
return false;
}
break;
default:
if (value)
{
return true;
}
break;
}
}
return value;
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.