6

It could be a silly question, but please help to answer it. Currently I have an interface with 2 generic methods:

ValidationResult Validate<T>(T t);


IList<ValidationResult> ValidateList<T>(IEnumerable<T> entities);

What I want is if you want to validate an object, use Validate method; if you want to validate an array of object, use ValidateList method, pretty clear in mind and interface. But it seems user can also use Validate method for a list of object without any compiler errors (of course!). Any there ways to restrict them to ValidateList method? Thank you so much.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
TuanHuynh
  • 137
  • 1
  • 5
  • 2
    add to your interface declaration – x4rf41 May 06 '13 at 14:45
  • 2
    @TimSchmelter True, but you can pass a list to `Validate`, which is what he doesn't want – Servy May 06 '13 at 14:47
  • 1
    Validate with one object is a subset of the functionality provided by the ValidateList method. Why not just have one method specified in the interface IList Validate(IEnumerable entities); where they can pass in an IEnumerable of one item if that is all they need validated. is there a strong case for breaking out the special case of one item? – Steven Magana-Zook May 06 '13 at 14:48
  • 2
    Why should someone be prohibited from passing a list to `Validate`? It's still an object; do you consider it *illegal* to validate a list? – Servy May 06 '13 at 14:48
  • 2
    I think [this question](http://stackoverflow.com/questions/8727523/generic-not-constraint-where-t-ienumerable) is related and the answer is: No. – Tim Schmelter May 06 '13 at 14:50
  • @x4rf41 This is validation service so actually i don't want to add to interface declaration as it restricts my concrete class to specific type of validation, i.e B Validation Service or A Validation Service. – TuanHuynh May 06 '13 at 14:59
  • 1
    You can't have negative generic constraints (ie, everything except `someClass`}. The only reason that generic constraints exists is to allow the compiler to know what signature to expect on the generic type. It is not to provide the developer with design-time cues. – Steve Konves May 06 '13 at 15:03
  • You want Validate to validate object and a List is an object. You want to negate and according to the link above from Tim that is not possible. – paparazzo May 06 '13 at 15:07
  • Yeah, you're right. That's kind of I'm actually expecting, negative generic constraints. Sounds like we do not have the answer here! – TuanHuynh May 06 '13 at 15:08
  • Is there a particular reason you want to use generics at all here? I mean, you'll have to write custom validation logic for each class you might pass to the method, right? Seems like that kinda defeats the purpose of using generics. Or else the entity classes themselves will have to contain their own validation logic, in which case it seems like the `Validate` method doesn't really have to care what type they are. Could you just have separate overloads for `Validate(FirstEntityType value)`, `Validate(SecondEntityType value)`, and so on? – Jeremy Todd May 06 '13 at 15:18
  • Hi @JeremyTodd this interface is exposed to outside. There are only one concrete class of this interface. In that class we use strategy pattern to pick the right validator one through typeof(T). This is internal control so you can have many strategies as you want but view from outside there is only one validation service. – TuanHuynh May 06 '13 at 15:26
  • 1
    @TuanHuynh I understand that, but any type constraints you specify for the generic method could simply be rewritten as the parameter type. For example `Validate(T value) where T : MyEntityType` could just be rewritten `Validate(MyEntityType value)` and save a little headache. Unless the parameters passed to the method have to implement multiple interfaces not supported by the base class or something... – Jeremy Todd May 06 '13 at 15:31
  • @JeremyTodd We also can do that way but we would end it up with headache when application grows up and cannot add new method as we go... – TuanHuynh May 06 '13 at 15:41
  • ...what about the Interface have a Validate `itself`, and an extension method to validade lists using the `itself` method? – Daniel Möller May 06 '13 at 16:28

4 Answers4

2

You can restrict a function to specific object types (and its derivatives) by doing something like this in the function declaration:

ValidationResult Validate<T>(T t) where T : ValidateBase, ValidateBaseOther

edit:

so in this case, the function will only take objects that are ValidateBase or its derivatives, or ValidateBaseOther or its derivatives. You can of course just use one if you want

ValidationResult Validate<T>(T t) where T : ValidateBase
David S.
  • 5,965
  • 2
  • 40
  • 77
  • Note that they can still hardcast the object to invoke the other signature. – Myrtle May 06 '13 at 14:47
  • 3
    @Aphelion I am not sure what you mean. If the object is not of the type then the cast would fail right? – David S. May 06 '13 at 14:48
  • you can cast to the basetype with `(ValidateBase)` and thereby enter the non-other variant – Myrtle May 06 '13 at 14:52
  • @Aphelion What's the 'non-other variant'? If you cast an object `(ValidateBase)object`, and the object isn't a `ValidateBase`, then you'd get `InvalidCastException`. – David S. May 06 '13 at 14:57
  • 1
    Unfortunately in this case, type parameter constraints are "and-ed" together, not "or-ed," so `where T : ValidateBase, ValidateBaseOther` won't compile unless `ValidateBase` and `ValidateBaseOther` are in the same inheritance chain (and I'm not sure even then), in which case you might as well just pick the most specific one. – Jeremy Todd May 06 '13 at 15:34
-1

In short, you can't solve your problem (prevent IEnumerable<> from being passed to a generic method) with generics. This is because you can't have negative generic constraints (ie., everything except someClass). The only reason that generic constraints exists is to allow the compiler to know what signature to expect on the generic type. It is not to provide the developer with design-time cues.

Aside from any unspecified external requirements, you can define your generic type at the interface, rather than the method. This does not ensure that T cannot implement IEnumerable<>; however, it does ensure that ValidateList must accept a list of whatever is passed to Validate. This means that T can be an array, but that would force ValidateList to accept an array of arrays.

If you leave the generic types at the method level, even though they both have an generic type named T, those types don't actually have anything to do with each other.

Generic type defined at interface.

public interface ISomeInterface<T>
{
    ValidationResult Validate(T t);
    IList<ValidationResult> ValidateList(IEnumerable<T> entities);
}

This would be implemented like:

public class SomeClass<T> : ISomeInterface<T>
{
    ValidationResult Validate(T t)
    {
        // Do something ...
    }
    IList<ValidationResult> ValidateList(IEnumerable<T> entities)
    {
        // Do something ...
    }
}

And used like this:

var myInstance = new SomeClass<int>();

int obj = 5;
int arr = new [] {1,2,3};

myInstance.Validate(obj);
myInstance.ValidateList(arr);

myInstance.Validate(arr); // should throw compiler error
Steve Konves
  • 2,648
  • 3
  • 25
  • 44
  • The only reason this is creating the desired result is because you're removing type inference and specifying a generic type, which is what results in the compiler error. If the generic type you specified was `IEnumerable` then the "errored" method would compile and the other two would fail. Therefore this isn't stopping you from using a list in the `Validate` method. – Servy May 06 '13 at 14:57
  • Technically, that is fine because then `ValidateList` would require a `IEnumerable>` which is a jagged array vs just an array. – Steve Konves May 06 '13 at 15:00
  • Thanks for your answer but since I don't want to add to interface declaration because it restricts my concrete class to specific one. Think if we have 20 objects so we have to make 20 concrete class of that interface... The purpose of this interface is to validate various kind of objects. Anyway, thanks. – TuanHuynh May 06 '13 at 15:03
  • Ok, but see me last comment on the question. – Steve Konves May 06 '13 at 15:04
  • @SteveKonves The point is that this isn't adding the assertion that the OP is asking for, and it's adding assertions that he's not asking for. The only reason your example appears to work is because it's removing type inference and thus when you specified the generic type you, at that type, restricted the possible types each method could accept. You can still pass whatever you want to either method by specifying the appropriate generic argument when creating the concrete class. – Servy May 06 '13 at 15:07
  • That is absolutely correct, but that type of assertion cannot be done with generics at compile-time. However, it CAN be done with reflection at run-time. My approach (using generics) was basically to ensure the correct relationship between the two methods, assuming that the interface was implemented correctly. – Steve Konves May 06 '13 at 15:11
  • But that's not his goal. The idea is to have two general purpose validate methods, one for sequences and one for non-sequences. This only makes it harder to use such methods, not easier, it doesn't actually enforce the assertion the OP wanted, etc. The main point here is that this clearly doesn't help the OP solve his problem (in addition to not technically answering the question). – Servy May 06 '13 at 15:22
-1

Since no better option was found: Check if the type passed to Validate implements IEnumerable<>. If so, maybe raise an exception....

So, here it goes:

ValidationResult Validate(T t)
{
    Type OutResultNotUsed;
    if (typeof(T).IsOrInherits(typeof(IEnumerable<>), out OutResultNotUsed))
        throw new Exeption("glkjglkjsdg");
}

Extension Method to check if some Type Is or Inherits a generic definition.

    public static bool IsOrInheritsGenericDefinition(this Type ThisType, Type GenericDefinition, out Type DefinitionWithTypedParameters)
    {
        DefinitionWithTypedParameters = ThisType;

        while (DefinitionWithTypedParameters != null)
        {
            if (DefinitionWithTypedParameters.IsGenericType)
            {
                if (DefinitionWithTypedParameters.GetGenericTypeDefinition() == GenericDefinition)
                    return true;
            }

            DefinitionWithTypedParameters = DefinitionWithTypedParameters.BaseType;
        }

        return false;
    }
Daniel Möller
  • 84,878
  • 18
  • 192
  • 214
  • The question is specifically asking for compile time validation. – Servy May 06 '13 at 16:19
  • Ok, answer "It's not possible", it will help a lot. – Daniel Möller May 06 '13 at 16:20
  • This is a duplicate of another question which specifically states it's not possible. I have already voted to close the question as a duplicate of that other one, so there is no need to answer it. – Servy May 06 '13 at 16:26
-1

You could do the following:

public class MultipleValidationResults : ValidationResult, IList<ValidationResult>
{
    // stuff...
}

public ValidationResult Validate<T>(T t)
{
    if (t is IEnumerable)
        // use reflection to call return ValidateList<Y>(t)
        // (T is IEnumerable<Y>)
    // other stuff
}

MultipleValidationResults ValidateList<T>(IEnumerable<T> entities);
Tim S.
  • 55,448
  • 7
  • 96
  • 122