简体   繁体   中英

How to achieve similar design (from Java) that use interface and implementing classes, using delegates in C#

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:

  • Anyone is able to write it's own implementation of Predicate interface by creating class that implements Predicate
  • Anyone is able to write it's own implementation of Predicate interface by creating anonymous implementation during call of filtering method
  • There predefined set of class implementing Predicate, that covers many standard situation, so in most cases there is no need to write new implementation.
  • Also there is a utility class that provides several static methods like and, or, allOf, anyOf, that allows to combine Predicates given as input into a new predicate (internally it will return anonymous implementation of new Predicate)

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:

  • As far as I know there is no such thing like anonymous implementation of interface in C# (so no creation of new predicate at the moment of calling filter method, and my util class have not to be based on anonymous implementations, but second is not the real problem)
  • I feel this is not a C#-way of doing such things

I can try achieve similar design using delegates. My filter method will take collection of object and Predicate delegate. In such case:

  • I don't know how to achieve situation when one is able to write some type that will "implements" delegate. I imagine situation when someone will just write class with method that match signature of delegate - I don't feel good about this strategy, because if I have interface to implement - compiler will force me to have correct signature - and since I cannot "extends" or "implements" delegate - compiler will tell that I'm wrong only when I will try to pass "reference" to this method to my filtering method (Of course it will still be compile time error).
  • Anyone is able to write it's own implementation of Predicate interface by passing lambda, or pointing to some method with signature that match Predicate delegate. So here will be the same as in Java
  • I don't know how to achieve situation when I have predefined sets of Predicates. I can see of course that is simple from technical point of view - I can for example write one class with static methods that will match signature of delegate - each of method will cover one Predicate. But I have a feeling that this approach is little bit inconsistent - because there will be one place with predefined predicates, and user's predicates will be defined in another places (In Java, both predefined and user-defined predicates will be classes that implements interface)
  • If Predicate will be delegate - I can also write some Util class that can combine predicates in many ways. Here is also the same as in Java

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM