Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extension methods on a generic interface where T is Enumerable<K>

I'm implementing a fluent argument assertion library where the focus is in strong type checking on compile time. Intellisense should only show methods and extensions available for the asserted type.

I'm having problems resolving proper type arguments when creating an extension for IEnumerable.

Idea in the library is that you can call ThrowIf (or ThrowIfNot) on any type which will return you an assertion instance of type IAssertion:

public static IAssertion<T> ThrowIf<T>(this T t)
{
    return new IfAssertion<T>(t);
}

Now I want to check against IEnumerable if it contains a specific item. There will be two overloads where one takes the object of type T as a parameter and the other takes a function where to do the evaluation:

public static T1 Contains<T1, T2>(this IAssertion<T1> assertion, T2 item)
    where T1 : IEnumerable<T2>
{
    // assertion logic
    return assertion.Value;
}

public static T1 Contains<T1, T2>(this IAssertion<T1> assertion, Func<T2, bool> func)
    where T1 : IEnumerable<T2>
{
    // assertion logic
    return assertion.Value;
}

Everything goes fine when using the overload taking an instance of the actual type. But the latter one with the function compiler cannot infer the type arguments properly unless cast is made:

var list = new List<string>();
list.ThrowIf().Contains("foo"); // compiles
list.ThrowIf().Contains((string s) => false); // compiles
list.ThrowIf().Contains(s => false); // does not compile

Is there any way I could make the compiler happy without doing the cast for the function parameter?

More implementation details can be found from here: https://bitbucket.org/mikalkai/argument-assertions/overview

like image 891
mikalkai Avatar asked Aug 31 '25 00:08

mikalkai


1 Answers

Disclaimer: This answer is only valid if IAssertion can be made covariant.

Assuming that IAssertion is covariant, you don't necessarily need two generic type parameters T1 and T2 for the Contains methods. Instead, you specify IEnumerable in your interface directly and use only one generic type parameter like this:

public static IEnumerable<T> Contains<T>(this IAssertion<IEnumerable<T>> assertion, T item)
{
  // assertion logic
  return assertion.Value;
}

public static IEnumerable<T> Contains<T>(this IAssertion<IEnumerable<T>> assertion, Func<T, bool> func)
{
  // assertion logic
  return assertion.Value;
}

Then you can use the contains method like this:

var list = new List<string>();
list.ThrowIf().Contains("foo"); // compiles
list.ThrowIf().Contains((string s) => false); // compiles
list.ThrowIf().Contains(s => false); // compiles now too
like image 128
M.E. Avatar answered Sep 02 '25 18:09

M.E.