3

I'm not sure how to better word the question, but I've run into the following problem with trying to create a Dictionary of generic interfaces more than once. Often this has come about when trying to create registry type collections which handle different types:

namespace GenericCollectionTest
{
    [TestFixture]
    public class GenericCollectionTest
    {
        interface IValidator<T>
        {
            bool Validate(T item);
        }

        class TestObject
        {
            public int TestValue { get; set; }
        }

        private Dictionary<Type, IValidator<object>> Validators = new Dictionary<Type, IValidator<object>>();

        class BobsValidator : IValidator<TestObject>
        {
            public bool Validate(TestObject item)
            {
                if (item.TestValue != 1)
                {
                    return false;
                }
            }
        }

        [Test]
        public void Test_That_Validator_Is_Working()
        {
            var Test = new TestObject {TestValue = 1};
            Validators.Add(typeof(BobsValidator), new BobsValidator());

            Assert.That(Validators[typeof(TestObject)].Validate(Test));
        }
    }
}

However, compilation fails because BobsValidator is not assignable to parameter type IValidator. Basically, I don't need type safety outside of the validator, but once I'm inside, I don't the consumer of the interface to have to cast it to the type they want to use.

In Java, I could:

Dictionary<Type, IValidator<?>>

I know I can do something like this (ala IEnumerable):

interface IValidator
{
    bool Validate(object item);
}

interface IValidator<T> : IValidator
{
    bool Validate(T item);
}

abstract class ValidatorBase<T> : IValidator<T>
{
    protected bool Validate(object item)
    {
        return Validate((T)item);
    }

    protected abstract bool Validate(T item);
}

Then make the dictionary take IValidator instead and extend ValidatorBase, but it seems like there must be a better way that I'm not seeing. Or, is this just poor design overall? It seems like I need some kind of structure like this:

WhatTheHecktionary<T, IValidator<T>>

Thanks!

Nishmaster
  • 525
  • 3
  • 10
  • your abstract version is a great place to start. From there you just need a factory that will get you the correct implementation at run time. The problem is that all you can expose to your calling code is IValidator, so every implementation must have that method. – Caleb Mar 21 '14 at 23:06

1 Answers1

0

In order to assign BobsValidator to IValidator, your you need to make your interface generic parameter into a covariant, this would allow your IValidator to point to a more specific type like IValidator.

interface IValidator<out T>
{
   bool Validate(T item);
}

However, you will realize you can't compile because your interface is no longer type safe, so the compiler won't allow. So why it is no longer type safe? Imagine this:

using NUnit.Framework;
namespace GenericCollectionTest
{
    [TestFixture]
    public class GenericCollectionTest
    {
        //.NET Compiling Error:
        //"Invalid variance: The type parameter 'T' must be contravariantly valid ..."
        interface IValidator<out T>
        {
            //Error: "Parameter must be type-safe. Invalid variance..."
            bool Validate(T item);
        }

        class MyObject
        {
            public int TestValue { get; set; }
        }

        class YourObject
        {
            public int CheckValue { get; set; }
        }

        class MyValidator : IValidator<MyObject>
        {
            public bool Validate(MyObject item)
            {
                return (item).TestValue == 1;
            }
        }

        class YoursValdator : IValidator<YourObject>
        {
            public bool Validate(YourObject item)
            {
                return (item).CheckValue == 1;
            }
        }

        [Test]
        public void Test_That_Validator_Is_Working()
        {
            //.NET compiler tries to prevent the following scenario:

            IValidator<object> someObjectValidator = new MyValidator();
            someObjectValidator.Validate(new YourObject()); // Can't use MyValidator to validate Yourobject

            someObjectValidator = new YoursValdator();
            someObjectValidator.Validate(new MyObject()); // Can't use YoursValidator to validate MyObject

        }
    }
}

To work around this, I suggest you try using non-generic as a base class so that you can store your validator in a dictionary. See if the following works for your case:

using System;
using System.Collections.Generic;
using NUnit.Framework;
namespace GenericCollectionTest
{
    [TestFixture]
    public class GenericCollectionTest
    {

        interface IValiadtor
        {
            bool Validate(object item);
        }

        abstract class ValidatorBase<T> : IValidator<T>
        {
            public bool Validate(object item)
            {
                return Validate((T)item);
            }

            public abstract bool Validate(T item);
        }

        interface IValidator<T> : IValiadtor
        {
            //Error: "Parameter must be type-safe. Invalid variance..."
            bool Validate(T item);
        }

        class MyObject
        {
            public int TestValue { get; set; }
        }

        class YourObject
        {
            public int CheckValue { get; set; }
        }

        class MyValidator : ValidatorBase<MyObject>
        {
            public override bool Validate(MyObject item)
            {
                return (item).TestValue == 1;
            }
        }

        class YoursValdator : ValidatorBase<YourObject>
        {
            public override bool Validate(YourObject item)
            {
                return (item).CheckValue == 1;
            }
        }

        [Test]
        public void Test_That_Validator_Is_Working()
        {
            Dictionary<Type, IValiadtor> Validators = new Dictionary<Type, IValiadtor>();
            Validators.Add(typeof(MyObject), new MyValidator() );
            Validators.Add(typeof(YourObject), new YoursValdator());

            var someObject = new MyObject();
            someObject.TestValue = 1;
            Assert.That(Validators[someObject.GetType()].Validate(someObject));


        }
    }
}
dsum
  • 1,433
  • 1
  • 14
  • 29
  • Yes, if you notice at the bottom of the question I suggest this very approach. I was wondering if there was a different approach other than this one. Thanks, though! – Nishmaster Mar 24 '14 at 13:43
  • I don't know if there is other better way due to the type safe issue. If you read my example, it is very clear why .NET designers do not want that. I have been using this base class technique many times and has been working very well. It enforce my team to code using the interface and it allows your architecture or framework to work with the specific type. One last thing you may want to try is returning a validation delegate (Predicate in this case), but I don't know how it helps you in your above scenario. – dsum Mar 24 '14 at 19:06
  • Although I tried to avoid going down this route, this was indeed the right answer. Event though I suggested it, I'm giving the answer to this one. Cheers! – Nishmaster Apr 11 '14 at 22:19