C
C#4w ago
Faker

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
Faker
FakerOP4w ago
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
Angius
Angius4w ago
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 ints Or you can have a
public class BaseEntity<TKey>
{
public TKey Key { get; set; }
}
public class BaseEntity<TKey>
{
public TKey Key { get; set; }
}
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 objects 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 results
AlkalineLemon
AlkalineLemon4w ago
generics 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?
Angius
Angius4w ago
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 performance
AlkalineLemon
AlkalineLemon4w ago
so, to store a value type in a reference type(like object). You have to put your value type in the
object
object
cap5lut
cap5lut4w ago
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)
AlkalineLemon
AlkalineLemon4w ago
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 overhead
MODiX
MODiX4w ago
cap5lut
REPL Result: Failure
object[] arr = new object[1];
arr[0] = (byte)1;
int value = (int)arr[0];
object[] arr = new object[1];
arr[0] = (byte)1;
int value = (int)arr[0];
Exception: InvalidCastException
- Unable to cast object of type 'System.Byte' to type 'System.Int32'.
- Unable to cast object of type 'System.Byte' to type 'System.Int32'.
Compile: 291.560ms | Execution: 20.593ms | React with ❌ to remove this embed.
cap5lut
cap5lut4w ago
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 safe
Faker
FakerOP4w ago
yeahh, 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
cap5lut
cap5lut4w ago
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?
Faker
FakerOP4w ago
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
cap5lut
cap5lut4w ago
boxing is making a value type a reference type, basically putting it into a box onto the heap
Faker
FakerOP4w ago
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 ?
cap5lut
cap5lut4w ago
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
Faker
FakerOP4w ago
yeah I see
cap5lut
cap5lut4w ago
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 appart
Faker
FakerOP4w ago
ahhhh 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?
cap5lut
cap5lut4w ago
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 known
Faker
FakerOP4w ago
yepp 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:
C#
// Generic method to add two numbers of any type
public T Add<T>(T a, T b)
{
return (dynamic)a + (dynamic)b; // Dynamically adds the numbers
}
C#
// Generic method to add two numbers of any type
public T Add<T>(T a, T b)
{
return (dynamic)a + (dynamic)b; // Dynamically adds the numbers
}
We can use generics:
C#
// Generic method to add two numbers of any type
public T Add<T>(T a, T b)
{
return a + b;
}
C#
// Generic method to add two numbers of any type
public T Add<T>(T a, T b)
{
return a + b;
}
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
cap5lut
cap5lut4w ago
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 😂
Faker
FakerOP4w ago
np, take your time, from I've understand, generics don't have the + operator
cap5lut
cap5lut4w ago
lets take a look at IComparable and IComparable<T>: the interfaces are easy:
interface IComparable
{
int CompareTo(object other);
}
interface IComparable<T>
{
int CompareTo(T other);
}
interface IComparable
{
int CompareTo(object other);
}
interface IComparable<T>
{
int CompareTo(T other);
}
a typical implementation would be something like
struct MyNumber : IComparable, IComparable<MyNumber>
{
public int CompareTo(MyNumber other) => ...; // IComparable<MyNumber> implementation
int IComparable.CompareTo(object other) => ...; // IComparable implementation
}
struct MyNumber : IComparable, IComparable<MyNumber>
{
public int CompareTo(MyNumber other) => ...; // IComparable<MyNumber> implementation
int IComparable.CompareTo(object other) => ...; // IComparable implementation
}
AlkalineLemon
AlkalineLemon4w ago
if your generic type implements the IAdditionOperators<> interface, i then you can add them with the + operator
cap5lut
cap5lut4w ago
interfaces 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:
int Compare<T>(T left, T right)
where T : IComparable<T>
{
return left.CompareTo(right);
}
int Compare<T>(T left, T right)
where T : IComparable<T>
{
return left.CompareTo(right);
}
and thus u get all the functionality of that interface
Faker
FakerOP4w ago
ahhh 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 ?
cap5lut
cap5lut4w ago
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>)
AlkalineLemon
AlkalineLemon4w ago
can you make instances of an interface? how does this Compare function even work?
Faker
FakerOP4w ago
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 !!
Thinker
Thinker4w ago
(technically that's not related to IAdditionOperators, it's just operator overloading)
cap5lut
cap5lut4w ago
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
AlkalineLemon
AlkalineLemon4w ago
oh and then you'd have to unbox it by type casting
cap5lut
cap5lut4w ago
no because u have it as IInterface u have all the functionality u need
AlkalineLemon
AlkalineLemon4w ago
if an int gets boxed as an interface, how can i read or do stuff on it? without unboxing
cap5lut
cap5lut4w ago
interface INameProvider
{
string GetName();
}
struct MyStructProvider : INameProvider
{
string GetName() => "spongebob";
}

void PrintGreeting(INameProdivder provider) => Console.WriteLine($"Hello, {prodiver.GetName()}");
interface INameProvider
{
string GetName();
}
struct MyStructProvider : INameProvider
{
string GetName() => "spongebob";
}

void PrintGreeting(INameProdivder provider) => Console.WriteLine($"Hello, {prodiver.GetName()}");
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 interface
AlkalineLemon
AlkalineLemon4w ago
yeah, 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
cap5lut
cap5lut4w ago
let me think during a ciggy break how to explain it
AlkalineLemon
AlkalineLemon4w ago
its fine ive already learned quite a lot i didnt even know what boxing/unboxing was a few minutes ago thanks for the explanation
cap5lut
cap5lut4w ago
ill explain it anyway, i just need to feed my addiction ;p
AlkalineLemon
AlkalineLemon4w ago
why wouldn't something like this work?
No description
Thinker
Thinker4w ago
Because operators are static Technically num * num is int.*(num, num)
AlkalineLemon
AlkalineLemon4w ago
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 type
Thinker
Thinker4w ago
When 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.
AlkalineLemon
AlkalineLemon4w ago
oh i see, i cant call the operator on the interface objects
Thinker
Thinker4w ago
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
static T Square<T>(T num) where T : IMultiplyOperators<T, T, T>
{
return num * num;
}
static T Square<T>(T num) where T : IMultiplyOperators<T, T, T>
{
return num * num;
}
AlkalineLemon
AlkalineLemon4w ago
it's an error because its two interface instances being multiplied, not two instances of a type that implements IMultiplyOperators yup that makes sense
Thinker
Thinker4w ago
yep
cap5lut
cap5lut4w ago
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.
Faker
FakerOP3w ago
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"
Angius
Angius3w ago
public T DoStuffWithList<T>(List<T> things)
public T DoStuffWithList<T>(List<T> things)
Takes a list of T as a parameter, and returns a single T
int a = DoStuffWithList<int>(new List<int> { 1, 2, 3 });
string b = DoStuffWithList<string>(new List<string> { "foo", "bar", "baz" });
int a = DoStuffWithList<int>(new List<int> { 1, 2, 3 });
string b = DoStuffWithList<string>(new List<string> { "foo", "bar", "baz" });
etc
Faker
FakerOP3w ago
yeah 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 ?
Angius
Angius3w ago
Yeah
Faker
FakerOP3w ago
Thanks !
Faker
FakerOP3w ago
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
No description
Faker
FakerOP3w ago
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 ?
Angius
Angius3w ago
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 all
Faker
FakerOP3w ago
yep 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 ?

Did you find this page helpful?