-1

I am facing the following problem with conversion of the inner type in Predicate<T>. I have a class implementing a basic interface. Additionally to the interface, the class has some advanced properties and methods.

public interface IBasic { /* ... */ }
public class Advanced : IBasic { /* ... */ }

In a method I get a number of predicates over the Advanced class and I would like to sort them into two lists: 1) predicates that use only the IBasic interface 2) predicates that need all features from the Advanced class.

List<Predicate<IBasic>> basic = new List<Predicate<IBasic>>();
List<Predicate<Advanced>> advanced = new List<Predicate<Advanced>>();

public void SetPredicates(params Predicate<Advanced>[] predicates)
{
    foreach(var item in predicates)
    {
        //Of course the `is` keyword does not work here, always returning false.
        //Is it possible to do the check on the lambda function in item in a different way?
        if (item is Predicate<IBasic>)
            basic.Add((Predicate<IBasic>)item); //And this cast throws an exception of course.
        else
            advanced.Add(item);
    }
}

Questions

  • Is it possible at all?
  • How can I check if the generic type of the predicate can be reduced to the IBasic interface?
  • How can I perform the predicate cast?

The number of predicates is low, so I am fine using slower things like reflection of dynamic types.

Background

The reason for the partition of predicates is that I have lots of Advanced instances. I want to pre-filter them first by predicates that require only the IBasic interface as those evaluate very fast. Then I will have to filter a much lower number of instances in the second pass with complex methods of Advanced (not in IBasic) since they take very long to evaluate.

Isolin
  • 845
  • 7
  • 20
  • This seems to completely break the [Liskov Substitution Principle](https://stackoverflow.com/questions/4428725/can-you-explain-liskov-substitution-principle-with-a-good-c-sharp-example). Sounds to me like something went wrong in the design, and now you have an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) – Camilo Terevinto Aug 23 '20 at 19:59
  • 1
    Have a look at [Covariance and Contravariance in Generics](https://learn.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance) – Pavel Anikhouski Aug 23 '20 at 20:20
  • @CamiloTerevinto Thank you for the suggestion. I don't see how it would break it. Look on it a different way and if you find a better design, please let me know: I have the class `Advanced` with various properties. Some properties evaluate quick but some need really long. I have lots of `Advanced` instances to check, each needs to comply to a few predicates. I wish to pre-filter them using the fast predicates first. To identify which predicates are the *fast* ones, I extracted the *fast* properties to `IBasic` interface and now I am trying to do the type checking & casting. – Isolin Aug 23 '20 at 20:44
  • @PavelAnikhouski Thank you! Yes I am trying to do a contravariant generic cast at runtime. From what I understood from the article it could work if I had the cast right at the lambda body, e.g. `SetPredicates((IBasic b) => b.Count > 1 )` but I have no control how the method is called. I will try to break it into two methods just ot see if that helps despite being worse design. – Isolin Aug 23 '20 at 21:20

2 Answers2

0

you could implement an overloaded Constructor in the Predicate Class. One for Basic and the other for Advanced. Also do a bool check on the item to see it is a basic check?

also maybe you can do some sort of boolean check on some property of the item to see if it is basic

List<Predicate<IBasic>> basic = new List<Predicate<IBasic>>();
List<Predicate<Advanced>> advaned = new List<Predicate<Advanced>>();

public void SetPredicates(params Predicate<Advanced>[] predicates)
{
    foreach(var item in predicates)
    {
        //Of course the `is` keyword does not work here, always returning false.
        //Is it possible to do the check on the lambda function in item in a different way?
        if (item is Predicate<IBasic>)
            basic.Add(new Predicate(item, isBasic)); //And this cast throws an exception of course.
        else
            advanced.Add(new Predicate(item));
    }
}
  • I am using the `Predicate` class from the `System` assembly. It is sealed, no chance to overload a constructor: https://learn.microsoft.com/en-us/dotnet/api/system.predicate-1?view=netcore-3.1 – Isolin Aug 23 '20 at 20:08
  • you could try this `public void SetPredicates(params Predicate[] predicates) { foreach(var item in predicates.FindAll( x => x is IBasic)) { basic.Add(item); } }` – Lokesh Podipireddy Aug 23 '20 at 20:24
  • Thank you for your effort, but `x => x is IBasic` is wrong, it should be `x => x is Predicate` which is for the CLR just the same as my example in the answer. – Isolin Aug 23 '20 at 20:30
0

@PavelAnikhouski pointed me to a right direction. The issue is that contravariance would be impractical for arrays. Nevertheless, it is possible to perform the contravariant cast at compile time by omiting params.

So instead of calling:

item.SetPredicates(x => !x.Empty /*IBasic*/, x => x.IsGlobalOptimum /*Advanced*/);

it can be broken into separate calls, each being correctly recognized by the compiler:

item.AddPredicate(x => !x.Empty); //IBasic
item.AddPredicate(x => x.IsGlobalOptimum); //Advanced

The Advanced class then has two overloaded methods:

public void AddPredicate(Predicate<IBasic> p)
{
    basic.Add(p);
}

public void AddPredicate(Predicate<Advanced> p)
{
    advanced.Add(p);
}

This is not the best interface, it would be nicer to be able to bulk add several predicates at once, but it works. In case anyone finds a more elegant solution, I will be glad to see and discuss it.

Isolin
  • 845
  • 7
  • 20