4 ms·
It is not only good advice, it is required in fair amount of situations because of limitations in the C# language.
by sharpercoder 9y ago
It is not only good advice, it is required in fair amount of situations because of limitations in the C# language.
- louthy 9y agoIt's terrible advice. What limitations are you thinking of?
- sharpercoder 9y agointerface IValidator<T> { bool Validate(T thingToValidate); } class ThingValidator : IValidator<Thing> {} class ThingValidator : IValidator<OtherThing> {} var validators = GetAllValidators(); What type is `validators`?
- louthy 9y agoThe signature of GetAllValidators must be: GetAllValidators<T>(); So it's whatever type of T you pass to GetAllValidators.
- deleted 9y ago[deleted]
- GenericsMotors 9y agoNot to mention that in the original example IValidator is invariant...it can be far more flexible by making it covariant (IValidator<out T>) so that GetAllValidators can also be used on more derived types. And then you can do for example: var validators = GetAllValidators<EntityBase>(); Where validators might be IEnumerable<IValidator<EntityBase>>. Compile-time type safety is preserved and each individual validator's generic type can be any type that is derived from EntityBase. :)
- lkitching 9y agoWhat sensible thing could you do with a list containing a ThingValidator and OtherThingValidator? Assuming there's no relationship between Thing and OtherThing you're forced to use runtime checking to find the validator you need for something typed as object, so at that point you have to use casting or reflection, which you'd also need to do when implementing a non-generic interface.
- ludston 9y agoYou are thinking that using reflection and casting at runtime is not a sensible thing because it is not optimal, but you must consider that constraints in existing code bases will often make writing optimal solutions too costly. An example is: For some reason or another you've written a thread dispatcher that will dynamically spin up threads and route the outputs of these threads into new threads. (e.g. https://github.com/bilus/pipes https://github.com/bilus/pipes ) Obviously this library will need to do run-time casting of types already as C# makes it too difficult to orchestrate these threads without some shared type. (Here is an example of Microsoft following this pattern. https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).... ) Now you need to hook validation into this existing dispatching library by assosciating validators with the outputs of particular threads. Without a shared type you cannot invoke the validator, but it sure is convenient to write the validator using generics. Another example is: You have two interfaces, "ISerializable" and "IEquatable", and two validators, "Validator<ISerizable>" and Validator<IEquatable>", and these validators apply to multiple classes. To run all of these validators against a given class you have three (un)reasonable choices: * Create a boilerplate wrapper class for each of these validators for each class that they apply to, and store them against that class somehow. The disadvantage of this is that you lose track of which validators are shared between classes which may be important for some optimisation. * Create a non-genic type "IValidator" or "Validator" that all validators share and put these in a collection. (i.e. as mentioned by the grandparent of this post). * Bite the bullet: The code that calls these validators has to be duplicated. (This is the incorrect choice though because some day you may need to add a spin-up/teardown before running the validators and now you don't have a centralized location to do this) Another example is: You are writing validators for types from a 3rd party library. You may not modify the source code of this library, because it's WinForms. You've been tasked with a set of arbitrary validation tasks for your multi-million line codebase. Maybe you need to add a spell checker for all user-editable text? Maybe any control that is bound to a particular property needs an orange background. Suddenly you have a case where you really do need to do casting and reflection to figure out which validators to run, as you perform the unfortunate evil of a recursive search through the entire control tree to hook up your validation, rather than re-writing a few thousand files.
- yumaikas 9y agoThis code wouldn't compile, for starters. But it could be `IValidator<object>`, in the worst case. That being said, why not make a `class ThingValidator<T> : IValidator<T> {}`, for all the lack of detail we have? There aren't enough details here to demonstrate the weaknesses of generics.
- sharpercoder 9y ago>This code wouldn't compile, for starters. Thats the point. You need an `IValidator` interface.
- yumaikas 9y agoThe reason it won't compile is that the code, as given, has two classes with the same name.
- Merad 9y agoThe question here is, what good is a collection of all validators? If you want to perform validation, you're starting with an item (to validate) and you know its type, so it shouldn't be a problem to get the correct IValidator<T> for it. If you really need to have a common base for all validators, explicit interface implementation is a better choice than hiding methods with new: interface IValidator { bool Validate(object item); } interface IValidator<T> : IValidator { bool Validate(T item); } abstract class Validator<T> : IValidator<T> where T : class { public abstract bool Validate(T item); bool IValidator.Validate(object item) { var typedItem = item as T; if (typedItem == null) throw new InvalidOperationException(); return Validate(typedItem); } }