0

I've created a validator that checks an IList<T> for duplicates. I came up with the following implementation:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var bar = new Foo() {Bar = "buzz"};
            var list = new List<Foo>() { }; //Foo has a property named Bar of type string
            var validator = new Validator<Foo, string>(x => x.Bar); //I have to specify the type of Bar
            validator.IsValid(list);
        }
    }
}

public class Foo
{
    public string Bar { get; set; }
}

public class Validator<T, Tkey>
{
    private readonly Func<T, Tkey> _keySelector;

    public Validator(Func<T, Tkey> keySelector)
    {
        _keySelector = keySelector;
    }

    public bool IsValid(IList<T> list)
    {
        if (list != null && list.Count > 0)
        {
            if (list.GroupBy(_keySelector).Any(g => g.Count() > 1))
            {
                return false;
            }
        }

        return true;
    }
}

But I don't like how It has to be used: I have to specify the type of Bar during construction.

The question is Can I somehow skip TKey when initializing Validator? Can it somehow be inferred?

hidarikani
  • 1,121
  • 1
  • 11
  • 25
  • 1
    For a start, your code doesn't even compile, there is no parameterless constructor and `IsValid` doesn't take a lambda. – DavidG Dec 23 '16 at 12:17
  • 2
    I don't completely agree with the 'marked as duplicate' for this question. The other post asks for *why* type inference isn't possible in constructors. This question is if there's another way. Which there is with a factory method (which is actually mentioned in the other posts' question) – Me.Name Dec 23 '16 at 12:24
  • So, for the record :) By using an extension method: `public static class SomeStaticClass { public static Validator GetValidator(this IEnumerable list, Func keyselector) { return new Validator(keyselector); } } ` You can simply use: `var list = new List(); //Foo has a property named Bar of type string var validator = list.GetValidator(x => x.Bar); //compiler knows T (Foo) from the list and TKey from the returned property` – Me.Name Dec 23 '16 at 12:26
  • I voted to reopen. It is not the same problem and I have an answer ready that I can't post now. – Peter B Dec 23 '16 at 12:26
  • My answer is about the same as Me.Name posted but it would be much more useful when not crammed into a comment. – Peter B Dec 23 '16 at 12:28
  • I made the example compile – hidarikani Dec 23 '16 at 12:30
  • 1
    The reopening saddens me. The [duplicate I chose](http://stackoverflow.com/questions/3570167/why-cant-the-c-sharp-constructor-infer-type) explains very well why this isn't possible, and that question itself already provides a workaround: use a static factory method, because it works for methods. "I already typed an answer" is no reason to reopen. Please close again as a duplicate, of [Generic Type in constructor](http://stackoverflow.com/questions/700966/generic-type-in-constructor) then. – CodeCaster Dec 23 '16 at 12:55
  • 1
    @CodeCaster In retrospect, I agree with you. I was too much distracted by this code blur in the comments and decided to reopen in order to at least get a better version of *that*. – Gert Arnold Dec 23 '16 at 16:47

3 Answers3

1

You can use an extension (factory) method to create the validator. The compiler will know how to resolve the types:

public static class SomeStaticClass
{
    public static Validator<T,TKey> CreateValidator<T,TKey>(this IEnumerable<T> list, Func<T,TKey> keyselector)
    {
        return new Validator<T,TKey>(keyselector);
    }
}

Now a validator can be created with:

var list = new List<Foo>(); //Foo has a property named Bar of type string
var validator = list.CreateValidator(x => x.Bar); //the compiler can infer T (Foo) through the list and TKey from the returned property inside the lambda 
Me.Name
  • 12,259
  • 3
  • 31
  • 48
1

You can use a generic extension method, which requires a separate static class. Generic methods can derive the T-types from their parameters.

Here is how:

public static class Extensions
{
    public static Validator<T, TKey> GetValidatorFor<T, TKey>(this List<T> list, Func<T, TKey> getter) where T : class
    {
        return new Validator<T, TKey>(getter);
    }
}

and then you can use it like this:

var list = new List<Foo>();
var validator = list.GetValidatorFor(x => x.Bar);
Peter B
  • 22,460
  • 5
  • 32
  • 69
0

You can use a temporary object to apply currying to the generic parameters:

static class Validator
{
    public static ValidatorConstructor<T> Construct<T>() => new ValidatorConstructor<T>();
}

class ValidatorConstructor<T>
{
    public Validator<T, TKey> WithSelector<TKey>(Func<T, TKey> selector) => new Validator<T, TKey>(selector);
}

class Validator<T, TKey>
{
    public Validator(Func<T, TKey> selector)
    {
    }
}

Usage:

Validator<Foo, Bar> e = Validator.Construct<Foo>().WithSelector(x => x.Bar);
Theodoros Chatzigiannakis
  • 28,773
  • 8
  • 68
  • 104