37

While reviewing some code, I noticed the opportunity to change it to use generics. The (obfuscated) code looks like:

public void DoAllTheThings(Type typeOfTarget, object[] possibleTargets)
{
    var someProperty = typeOfTarget.GetProperty(possibleTargets[0]);
    ...
}

This code could be replaced by generics, like so:

public void DoAllTheThings<T>(object[] possibleTargets[0])
{
    var someProperty = type(T).getProperty(possibleTargets[0]);
    ...
}

In researching the benefits and shortcomings of this approach I found a term called generic abuse. See:

My question comes in two parts:

  1. Are there any benefits to moving to generics like this? (Performance? Readability?)
  2. What is Generics Abuse? And is using a generic every time there is a type parameter an abuse?
SyntaxRules
  • 581
  • 1
  • 4
  • 13
  • 2
    I'm confused. You know the name of the property beforehand but not the type of the object so you use reflection to get a value? – MetaFight Feb 07 '18 at 17:35
  • 3
    @MetaFight I know it convoluted example, the actual code would be to long and difficult to put here. Regardless, the purpose of my question is to determine if replacing the Type parameter with a generic is considered good or poor form. – SyntaxRules Feb 07 '18 at 17:39
  • 12
    I've always considered that to be the typical reason why generics exist (delicious pun not intended). Why do type trickery yourself when you can leverage your type system to do it for you? – MetaFight Feb 07 '18 at 17:42
  • 2
    your example is wierd, but I have a feeling it is exactly what a lot of IoD libraries do – Ewan Feb 07 '18 at 17:47
  • 1
    You have two questions there. Split your question into two. – Euphoric Feb 07 '18 at 19:09
  • 14
    Not an answer, but it's worth noting that the second example is less flexible than the first. Making the function generic removes your ability to pass in a runtime type; Generics need to know the type at compile time. – KChaloux Feb 07 '18 at 19:34
  • 1
    @KChaloux that actually depends on the language. Are you from a Java background perhaps? – MetaFight Feb 07 '18 at 19:44
  • @MetaFight I'm from a C# background, which is admittedly different in that it has a reified type system that could potentially be different than how Java handles it. I haven't touched Java since college. – KChaloux Feb 07 '18 at 20:06
  • Hrm, I'm also from a C# background. Maybe I don't understand C# generics as well as I thought. I'll look into this. – MetaFight Feb 07 '18 at 20:10
  • 1
    @MetaFight KChaloux is correct. You can't do "Type t = arrayOfTypes[i]; GenericMethod();" in C#. ECMA-334 specifically states that only static type names are allowed within the arguments of a generic method call or generic type instancing. You can compile one on-demand with reflection, but that's far slower than just including a method that takes a Type argument. – Kevin Fee Feb 07 '18 at 21:37
  • Using Reflection you can make it work, but at that point you're still much better off leaving the Type as the first argument: [example](https://tio.run/##bVBdS8MwFH1ufkXYUwqzcdWJOPVFcAjOFws@jD2k2e0a1@aWJOsYo7@9ph/TFyHckMPJ@bjSXkkr25bzJWgwStISXI5bqnSNUjiFmmYGS5o7V9kHzq0Tco81mKzAYySx5ILHN/FdPOPxfH57P7sm5GCV3tHPk3VQLogWJdhKSKCCnElQHdLC28hCWEtTEnjognlt568a1ZauhNLMOuOV1hsqzM6GntiRg1qYS8on6k4VYMbSMFqCW/Uom2wxyf3HSbjo@BkaEDJniaf2fOXbUQ1H2iHrzXnUwPQbpAunF83B/e@dhk2XYQgRDAmildjDuLrRvXcIoze/wD0wfSiKKe3mEKYhZBz/lB5zPybP7LftC2qLBURfRjl4VxrYGCcJo1cv@@HX20s3xJ@mbX8A) – IllusiveBrian Feb 07 '18 at 21:38
  • OK, so my understanding wasn't that far off. I knew about the type literal limitation as well as the reflection trick. I didn't realise that the reflection trick resulted in compilation at runtime though. – MetaFight Feb 08 '18 at 00:12
  • @MetaFight Any language that has generics by definition has a static type system (otherwise there's simply no point for generics) in which case the only way to call a generic method with a runtime type is to do the same thing the compiler would have to do already. So I don't see how any language could do better than what C# is providing. And obviously any language can do what C# is doing - worst case you have to generate the required code by hand if need be (it might be very, very annoying to do so though). – Voo Feb 08 '18 at 19:57
  • @Voo I originally thought KChaloux was referring to how Java implements generics, but I was mistaken. I'm not saying C#'s generics are bad. I just misunderstood what was meant about the ability to pass a type at runtime. – MetaFight Feb 09 '18 at 00:10
  • The question is tagged java, but it looks like C#? – looper Feb 09 '18 at 07:42
  • @MetaFight My point isn't that C# or Java or any other language are bad, my point is that with the usual definition of generics that every mainstream language that has them generally agrees on, you can't pass in dynamic types at runtime without doing additional dynamic checks - that's true for C#, Java and anything else. – Voo Feb 09 '18 at 08:26
  • @looper Not sure when it was tagged java, its should not be. I tried to make my question generic enough (pun intended) to be applicable to more than one language. – SyntaxRules Feb 09 '18 at 16:02
  • @SyntaxRules: But this question is only applicable to a language which generates separate copies of the code for different instantiations. The second piece of code is not possible in a language like Java that implements generics with type erasure, because the method has no way to get what `T` is at runtime. The only way to have it at runtime would be to pass the type in explicitly. – user102008 Feb 25 '18 at 16:15
  • @KChaloux Note that this "lack of flexibility" is actually a strength of generic programming. In software engineering, constraining what a function can do by its type is a plus. A way to look at it: in a language where you can inspect the types, what can a function of type `(x: T, y: T) => T` do (where `T` is generic)? Well, maybe it inspects the arguments, sees they are `Int`s, and sums them! Or multiplies them! Or chooses one! Or makes up an entirely new `Int`! But in a language with no runtime type inspection, there's only two things it can do: it can either return the 1st elem or the 2nd – Andres F. Mar 01 '18 at 22:33
  • @KChaloux This kind of "lack of flexibility" is what allows the developer to rely on the generic function's signature: it cannot do things with types it cannot "know", and so there are fewer things it *can* do, and therefore fewer bugs. The fewer assumptions you make about the actual types, the fewer the bugs you tend to write. This is the basis of generic programming :) – Andres F. Mar 01 '18 at 22:34
  • @AndresF No arguments there. I wasn't making a value judgment as to whether you _should_ use an object instead of a type parameter, just noting that the examples given do not have the same behavior in some languages. – KChaloux Mar 06 '18 at 21:15

4 Answers4

88

When generics are appropriately applied, they remove code instead of just rearranging it. Primarily, the code that generics are best at removing is typecasts, reflection, and dynamic typing. Therefore generics abuse could be loosely defined as creating generic code without significant reduction in typecasting, reflection, or dynamic typing compared to a non-generic implementation.

In regard to your example, I would expect an appropriate use of generics to change the object[] to a T[] or similar, and avoid Type or type altogether. That might require significant refactoring elsewhere, but if using generics is appropriate in this case, it should end up simpler overall when you're done.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479
  • 3
    That's a great point. I'll admit the code that got me thinking of this was performing some hard to read reflection. I'll see if I can change `object[]` to `T[]`. – SyntaxRules Feb 07 '18 at 20:31
  • 3
    I like the remove vs. rearrange characterisation. – copper.hat Feb 07 '18 at 22:57
  • 13
    You missed one key use of generics - which is to reduce duplication. (Though you can reduce duplication by introducing any of those other sins you mention). If it reduces duplication without removing any typecasts, reflection, or dynamic typing IMO it is OK. – Michael Anderson Feb 08 '18 at 00:13
  • 4
    Excellent answer. I'd add that In this particular case, the OP may need to switch to an **interface** instead of generics. It looks as though the reflection is being used to access particular properties of the object; an interface is much better suited to allow the compiler to determine that such properties actually exist. – jpmc26 Feb 08 '18 at 02:48
  • 4
    @MichaelAnderson "remove code instead of just rearranging it" includes reducing duplication – Caleth Feb 08 '18 at 10:59
  • I think it's also acceptable to use generics if you're corralling all the reflection-/dynamic-/typecast-heavy code into one place. – Casey Feb 08 '18 at 19:57
  • @Caleth I agree that "remove code instead of just rearranging it" includes reducing duplication - I guess I was mostly objecting to the "Primarily, the code that generics are best at removing..." part. In my code I usually use generics to reduce duplication where none of those aspects are used. But maybe that is because I know those other techniques are usually bandaid solutions for problems that are better solved using Generics or Interfaces - so they never creep in to start with. – Michael Anderson Feb 09 '18 at 01:13
5

I would use the no-nonsense rule: Generics, like all other programming constructs, exist to solve a problem. If there is no problem to solve for generics, using them is abuse.

In the specific case of generics, they mostly exist to abstract away from concrete types, allowing code implementations for the different types to be folded together into one generic template (or whatever your language happens to call it). Now, suppose you have code that uses a type Foo. You might replace that type with a generic T, but if you only ever need that code to work with Foo, there is simply no other code with which you can fold it together. Thus, no problem exists to be solved, so the indirection of adding the generic would just serve to reduce readability.

Consequently, I'd suggest to just write the code without using generics, until you see a need to introduce them (because you need a second instantiation). Then, and only then, is the time to refactor the code to use generics. Any use before that point is abuse in my eyes.


This seems to sound a bit too pedantic, so let me remind you:
There is no rule without exceptions in programming. This rule included.

  • Interesting thoughts. However a slight rewording of your criteria could help. Suppose OP needs a new "component" that maps an int to a string and vice versa. It's very obvious that it solves a common need and could be easily reusable in the future if made generic. This case would fall in your definition of generics abuse. However, once the very generic underlying need identified, wouldn't it be worth to invest an insignificant additional effort in the design without this being an abuse ? – Christophe Feb 08 '18 at 08:07
  • @Christophe That's indeed a tricky question. Yes, there are some situations where it is indeed better to ignore my rule (there is no rule without exception in programming!). However, the specific string conversion case is actually rather involved: 1. You need to treat signed and unsigned integer types separately. 2. You need to treat float conversions separate from integer conversions. 3. You can reuse an implementation for the largest available integer types for smaller types. You may wish to pro-actively write a generic wrapper to do the casting, but the core would be without generics. – cmaster - reinstate monica Feb 08 '18 at 10:24
  • 1
    I'd argue against pro-actively writing a generic (pre-use rather than re-use) as likely YAGNI. When you need it, then refactor. – Neil_UK Feb 08 '18 at 19:33
  • Upon writing this question I took more of the stance of "Write it as a generic if possible to save future possible reuse." I knew that was a bit extreme. It is refreshing to see your point of view here. Thanks! – SyntaxRules Feb 08 '18 at 19:44
0

The code looks odd. But if we are calling it, it looks nicer to specify the type as a generic.

https://stackoverflow.com/questions/10955579/passing-just-a-type-as-a-parameter-in-c-sharp

Personally I don't like these contortions that make the calling code look pretty. for example the whole 'fluent' thing and Extension Methods.

But you have to admit that it has a popular following. even microsoft use it in unity for example

container.RegisterType<IInterface,Concrete>()
Ewan
  • 70,664
  • 5
  • 76
  • 161
0

To me it looks like you were on the right path here, but didn't finish the job.

Have you considered changing the object[] parameter to Func<T,object>[] for the next step?

sq33G
  • 280
  • 3
  • 12