0

I have a generic extension method like following:

public static class Validator
{
    public static IEnumerable<string> Validate<T>(IList<T> items)
    {
        foreach (var item in items)
        {
            var result = MyValidator.Validate(item);

            if (!result.Any()) continue;
            
            foreach (var validationResult in result)
                yield return validationResult.ErrorMessage;

        }
    }
    
    public static IEnumerable<string> Validate<T>(T item)
    {
            var result = MyValidator.Validate(item);

            if (!result.Any()) yield break;
            
            foreach (var validationResult in result)
                yield return validationResult.ErrorMessage;

    }
}

But when I call the method for single object or collection it calls Validate(T item);

var item = new Person();
var items = new List<Person>();

var v1 = Validator.Validate(item);  // calls Validate<T>(T item)
var v2 = Validator.Validate(items); // calls Validate<T>(T item) but should call Validate<T>(IList<T> items)

I wan to call Validate<T>(IList<T> items) for list.

Why this problem occured?

barteloma
  • 6,403
  • 14
  • 79
  • 173
  • 1
    var v1 = Validator.Validate(new List() {item}); – jdweng May 26 '21 at 12:39
  • 2
    It's not an extension method unless you declare its first param as `this IList items` – Tommaso Bertoni May 26 '21 at 12:46
  • 1
    Also, you seems to have a recursion. `Validate(T item)` calls itself: `item.Validate()`. Since `T` has no generic constraint, `Validate` cannot be a member of `item`. – Olivier Jacot-Descombes May 26 '21 at 12:47
  • 1
    I updated the post, sorry for title extension method. – barteloma May 26 '21 at 12:49
  • 2
    Does this answer your question? [Which C# method overload is chosen?](https://stackoverflow.com/questions/32892243/which-c-sharp-method-overload-is-chosen) –  May 26 '21 at 12:51
  • 2
    I recommend renaming one of those methods, preferably the one for List to something like `ValidateList`. – juharr May 26 '21 at 12:51
  • Or name it `ValidateAll` and have it take an `IEnumerable`; it does not specifically require an `IList`. Also, the call to `Any()` is redundant; an empty `foreach` is perfectly legal. – Jeroen Mostert May 26 '21 at 12:54
  • 1
    **Note:** if you change the method to accept a `List` you'd get the desired behavior => `Validate(List items)` – Tommaso Bertoni May 26 '21 at 12:55
  • 1
    The reason is fundamentally "because the conversion from `List` to `List` is better than the conversion from `List` to `IList`" but the details are buried in the language specification. (Overload resolution is particularly thorny.) Rather than worry about the exact details, I'd recommend renaming one of the methods so that you don't need to rely on overload resolution at all. – Jon Skeet May 26 '21 at 13:18

2 Answers2

0

These extension methods have similar signatures - IList<T> items is T item.

Renaming Validate<T>(IList<T> items) to ValidateList<T>(IList<T> items) or renaming Validate<T>(T item) to ValidateItem<T>(T item) would provide the result you are looking for.

Greg Kniffin
  • 19
  • 1
  • 2
-1

That's because interfaces have lower priority in overloads resolutions than concrete types
As @JonSkeet points out, it may be because the type conversion from T to T is better than List<T> to IList<T>, so the compiler chooses the generic overload.

In your case, you would get the desired behavior by changing the method's parameter from IList<T> to List<T>.

public interface IFoo { }
public class Foo : IFoo { }

public static class Validator
{
    public static void Method<T>(T t) { }
    public static void Method(object o) { }
    public static void Method(IFoo i) { }
}

static void Main()
{
    var foo = new Foo();
    Validator.Method(foo); // Invokes Method<T>(T t)

    object o = foo;
    Validator.Method(o); // Explicit object, invokes Method(object o)

    IFoo i = foo;
    Validator.Method(i); // Explicit interface, invokes Method(IFoo i)
}
Tommaso Bertoni
  • 2,333
  • 1
  • 22
  • 22
  • 2
    "That's because interfaces have lower priority in overloads resolutions than concrete types." No, that's not true. It's because in the call to the first method, the conversion is from `List` to `IList`; in the second it's from `T` to `T`. The latter is preferred because the types are identical - if `items` had been declared as `IList` then the first method would be used instead. – Jon Skeet May 26 '21 at 13:16
  • 1
    @JonSkeet sorry if I was not accurate, do you have a link to documentation explaining the resolution? I couldn't find any, so I tested my hypothesis as shown – Tommaso Bertoni May 26 '21 at 13:20
  • @JonSkeet declaring the method as `Validate(IList items)` doesn't seem to win against `Validate(T item)`, while `Validate(List items)` does. – Tommaso Bertoni May 26 '21 at 13:29
  • Yes, but that's not because `IList` is an interface. `Validate(object)` would work the same way. Basically, the details are all in the language specification in the section about overload resolution and "better conversions". – Jon Skeet May 26 '21 at 13:31