What is meant by "Generic" and where do we use them and why are they important
Hello guys, sorry to disturb you all; can someone explain what is meant by "generic" please; does this applies only to OOP concept? or any programming language with collections? Like I know the angular brackets is used to define a generic. But why do we need them? What would happen without generics?
58 Replies
For example, I'm reading that there are kind of 2 type of hashtable, a non-generic one and a generic one (dictionary)
why does it matter pls
It means there's a
HashTable
that can hold key-value pairs
And a Dictionary<TKey, TValue>
that can hold key-value pairs of specified type
To be clear, you should never use the former
Generics are a way to pass a type as a parameter
For example, a List<T>
can be List<int>
, telling the compiler that it will hold only int
s
Or you can have a
that will have the Key
of a specified type
var e = new BaseEntity<string>()
will make it so that e.Key
will be a string
To answer your question about world without generics... there was a time that C# did not have them
That's where ArrayList
and HashTable
are from
They would hold object
s and object, object
pairs
So you would need to cast everything
Instead of doing int x = listOfInts[3]
you would have to do int x = (int)listOfInts[3]
every time
Not to mention performance issues
https://github.com/Atulin/Solution1/tree/master/Collections
I gave a talk during Solution1 that touched upon non-generic collections
In this repo, you can find examples of using both generic and non-generic ones, as well as benchmark resultsgenerics let you make classes and methods that use a general type
For example,
if you wanted to make a method to swap two variables of the some time, you could do it in two ways
1- Write out swap methods separately for int, float, double, string, etc, etc
2- Use generics to make one method that can accept any two matching types(2 ints, 2 floats, 2 strings, etc
hmm, does type casting a lot reduce performance?
Not a lot, but some
Especially when casting from
object
object
is a reference type. If you store int
— a value type — as an object
, it has to be boxed
Then, when casting back to int
it needs to be unboxed
Both boxing and unboxing do impact performanceso, to store a value type in a reference type(like object). You have to put your value type in the
the most crucial part is imo the type safety.
int x = (int)listOfInts[3];
could break at any time for the non-generic types if something else sneaked its way in.
this could for example be a boxed byte
as well (and yes that would throw)and when you want to get the value type back, you have to take it out of the
object
i can see that adding a ton of overheadcap5lut
REPL Result: Failure
Exception: InvalidCastException
Compile: 291.560ms | Execution: 20.593ms | React with ❌ to remove this embed.
to answer
What would happen without generics?generic type parameters make classes/structs/interfaces/methods sort of like templates, in a safe manner but keep it DRY. without generics, you basically have two options: 1) use some horribly slow generalized collection like the mentioned
HashTable
or ArrayList
2) repeat your code a lot of times, like writing a ListInt
, a ListByte
, ListString
, etc.
taking collections as example here:
both approaches have their problems which the other fixes:
1) is basically one code base, so bug fixes apply to all collections, but they are not type safe (thus error prone) and due to stuff like boxing pretty slow.
2) has multiple codes, so applying bug fixes is maintenance hell because u have 135890235 types of the same collection but for different element types. its easy to miss one (thus error prone). on the other hand, each collection type is type safeyeahh, sorry, I was just reading a bit about generics and non-generics thing; from what I've understood:
Generics mainly ensure type safety? We must use generics collection instead of non-generics one, this prevent the overhead of casting an object to the required data type. Also, we don't write code multiple times for different data types as mentioned
I've a question, what do we mean by "boxing" and "unboxing", is it same as "Casting" ?
we say that when we pass an int to an ArrayList for e.g, that int is "box" into an object
and in order to retrieve that value, we need to "unbox" it using casing I think
with type safety here i mean btw, that u would get a compile error, so your application wouldnt build at all.
the unsafetiness (is that a word?) would be that its a runtime error that might occur at start up, or maybe causes a random crash of ur application in 3h with a 1% chance (have fun debugging that!)
do u know the difference between value types and reference types?
yep
value types = store copy of a value directly in memory, like int
reference types, we store the referece / memory address, like string/ any object
boxing is making a value type a reference type, basically putting it into a box onto the heap
ahh ok I see
then unboxing is when we take a reference type and make it becomes a value type ? it's no longer on heap but on stack then ?
sort of yes and sort of no.
you cant magically make any reference type a value type. unboxing is literally just to get back the value that was boxed
yeah I see
one of the reasons why boxing exists is that for arrays u want all elements to be of the same size
so that
arr[i]
and arr[i+1]
are exactly n
bytes appartahhhh
I see
was going to ask why 😂
but what happens internally? like each element will have same data type no? so they take same space?
that way the element access to each array element is basically
start + index * sizeOfElement
, instead of scanning through each element from start to see where its located in memory
with boxing they behave like any other reference type. u have a reference (or pointer or memory address) to the actual instance data.
this data contains some meta information (the actual type and some other stuff) + the actual data
and this is basically where generics help. instead of saying each element has to be a reference, u say each element has to be of "that type", and because of that "that type" is known when u actually use the generic, eg for List<int>
u know that "that type" is 4 bytes and List<string>
is 8 bytes (well on 32 bit processes its 4 bytes but thats another topic).
its to make the element size uniform, but without making it a constant size
without generics, the only way is to make the size uniform by making everything reference types which requires boxing
with generics, u dont have to use boxing to make it uniform because the size is knownyepp I see
Thanks guys really appreciate, at the start of the discussion I barely know what generics was but I believed I have a clearer understanding now.
last thing, if I understood generics, say we need to build a calculator. Instead of overloading different method to add different data types, like int + int or double + double, like below:
We can use generics:
Now, can we have several "generics", like I want a to be of any numeric data type, b also of any numeric data type so that I can add an int and a double. Then I want the return type to be of any numeric data type depending on what result a + b gives
thats something i wanted to come to now, like this ur
Add<T>
method will not compile
because T
could be any type, but not all types have a +
operator
for that there are so called generic constraints
basically instead of saying that T
can be anything, u tell it T
has to be any type of something.
im just thinking about a good example for it right now 😂np, take your time, from I've understand, generics don't have the
+
operatorlets take a look at
IComparable
and IComparable<T>
:
the interfaces are easy:
a typical implementation would be something like
if your generic type implements the
IAdditionOperators<>
interface, i then you can add them with the + operatorinterfaces are reference types.
so if if u would do something like
int Compare(IComparable left, IComparable right) => left.CompareTo(right);
u would box both of the MyNumber
instances, which is bad
but by making it a generic method and constraining T
to an interface, it would mean "any type that implements that interface" is allowed:
and thus u get all the functionality of that interfaceahhh I see
so basically, at first we say, we want every type but then we just restrict to have ONLY the types of the interface that it implements ?
if u now use
MyNumber
as T
, it is also known that its a struct, and that interface and thus doesnt have to box
more or less yes.
there are a lot of different kind of generic constraints. i chose interface constraint, because its easier to explain ;p
there is for example also a constraint to only allow structs (where T : struct)
, and also to allow only structs that do not contain any reference types (where T : unmanaged
).
and ofc u can also combine that (where T : unmanaged, IComparable<T>
)can you make instances of an interface? how does this Compare function even work?
there are a lot more, u can read about it here: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters
yep I see, I was having a look at that, seems complicated but I would have to learn that later on, good to know the generics constraints though, thanks !!
(technically that's not related to
IAdditionOperators
, it's just operator overloading)u cant, but if u pass a value type that implements the interface to something that takes the interface, the value type will be boxed
oh
and then you'd have to unbox it by type casting
no
because u have it as
IInterface
u have all the functionality u needif an
int
gets boxed as an interface, how can i read or do stuff on it?
without unboxing
if u pass a
MyStructProvider
instance to PrintGreeting
, it will be boxed, but u can just use it anyway, because u just care about the interface
u have to think about it differently.
u write a method for that interface. the int just happens to implement that interfaceyeah, IAdditionOperators<> just forces you to implement an overload for the '+'
from what im getting, you can do all of the stuff that the interface implements on the boxed int?
its confusing
let me think during a ciggy break how to explain it
its fine
ive already learned quite a lot
i didnt even know what boxing/unboxing was a few minutes ago
thanks for the explanation
ill explain it anyway, i just need to feed my addiction ;p
why wouldn't something like this work?

Because operators are static
Technically
num * num
is int.*(num, num)
it gives an error saying that
*
can't be used on operands of type IMultiplyOperators<int, int, int>
yes that makes sense, there's only one *
operator(its not associated with int
instances, im probably saying this weirdly) for the entire int
typeWhen you use an operator through an interface, you need to call it on the type itself. Remember, you can imagine
*
here as T.*(num, num)
, but there is no type T
to call it on.oh i see, i cant call the operator on the interface objects
When you pass an instance of
IMultiplyOperators
, there is no type to call the operator on
What you need to do is to pass it as a type parameter constrained to the interface it's an error because its two interface instances being multiplied, not two instances of a type that implements
IMultiplyOperators
yup that makes senseyep
thinker explained what i wanted to explain already 😂
generally speaking u have to get away from the thought that "this method will be called with an int"
u write a method consuming types of a specific functionality, which is specified by an interface.
int might be a type that fits that interface, or it might not be.
its the user of that method's responsibility to pass appropriate parameters.
By the way, to use generics, say I want to create a method that will take as parameter an array of "any" type; how is the syntax written pls; how does it differ if I want to return something of "any type"
Takes a list of
T
as a parameter, and returns a single T
etcyeah I see
whenever we declare a generic method or a generic class, the angular brackets means that our method is expecting a type parameter or our class is expecting a type field/instance variable ?
Yeah
Thanks !
Hello guys, can someone explain what is happening here, I understood that we are declaring a generic public class. The TKey and TValue represents 2 type variables. What confuses me is the private class defined, what's happening now, it seems we are using the outer class generic property inside the private class

Also, I have a question. When we want to use a type variable, say I want to declare a type array, can I do it directly in our main entry point? Like in a Top level statement?
Or I should create a new generic class then in the fields/property make that class accept a type variable ?
Yep, that's exactly what's going on here
You can have a, say,
List<int> nums = [ 1, 2, 3, 4, 5 ];
, yes
But you can't make the top-level statements code somehow generic
There's no Program
class nor Main
method that could have a type parameter, after allyep I see, here how is it possible that we use the generic property of an outer class inside another class pls, like if we would make the private class a generic method, it would feel like "normal"; is there another scenario where we can kind of use this kind of generic structure pls
we can't have any generic type in our main method then, UNLESS behind the scenes, generics have explictly been defined, like in a linkedList ?