C# Basics - Generic
2023-02-12 23:36:40  C#  >> Basics

Purpose of Generic

When we program, we often come across code blocks with very similar structure but handle different data. We have no choice but to write multiple instance calls to handle different data types. At this point, is there a way to use the same method to deal with different types of parameters passed in? The Generic is specifically to solve this problem.

Let’s look at some codes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
namespace Generic
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
Test1 t1 = new Test1();
Test2 t2 = new Test2();
Test3 t3 = new Test3();
}
}

public class Test1
{
public string v1;
public string v2;
public string v3;

public void Test(string v)
{
}
}
public class Test2
{
public int v1;
public int v2;
public int v3;

public void Test(int v)
{
}
}

public class Test3
{
public double v1;
public double v2;
public double v3;

public void Test(double v)
{
}
}
}

Test1, Test2, and Test3 have the same class structure, the only difference is they are handling different type of fields. Once we are required to handle another type of data, like float, we have to create another class specilized for float type, which is unacceptable and inconvinince.

Generic will help solve the problem.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
namespace Generic
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Call generic class with the needed type.
TestGeneric<float> testGeneric = new TestGeneric<float>();
}
}

public class TestGeneric<T>
{
public T v1;
public T v2;
public T v3;

public void Test(T v)
{

}
}
}

Create a Generic is to add <T> to the class and change the type of fields to T. When you wanna use the class, simply call the class with replace T with the type you need in < > as below.

1
TestGeneric<float> testGeneric = new TestGeneric<float>();

Generic Type Constraints

C# allows you to use constraints to restrict client code to specify certain types while instantiating generic types. The way to add constraint is to use where keyword after a generic difinition.

1
public class Test<T> where T: [Type]

Let’s look at the previous example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace Generic
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Call generic class with the needed type.
TestGeneric<float> testGeneric = new TestGeneric<float>();
}
}

//Add constratint of struct to generic class.
public class TestGeneric<T> where T:struct
{
public T v1;
public T v2;
public T v3;

public void Test(T v)
{

}
}
}

where T:struct means the class is being restricted as using only value type.

1
2
3
4
5
//int is a value type, so it is valid to be called by generic class.
TestGeneric<int> testGeneric = new TestGeneric<int>();

//string is reference type, so it returns error.
TestGeneric<string> testGeneric2 = new TestGeneric<string>();

Alt text

Based on different types of constraint, Generic can be used in many ways.

Generic Methods

This is a method to get the max one between two values.

1
2
3
4
public static T Max<T>(T a, T b) where T : IComparable<T>
{
return a.CompareTo(b) > 0 ? a : b;
}
1
2
int maxInt = Max(1, 2);
double maxDouble = Max(1.2, 3.4);

Generic Interfaces

This is a Generic Interfaces can be used for database management.

1
2
3
4
5
6
7
public interface IRepository<T> where T : class
{
T GetById(int id);
void Add(T entity);
void Update(T entity);
void Delete(T entity);
}
1
2
3
4
public class ProductRepository : IRepository<Product>
{
// implementation goes here...
}

Generic Delegate

This is a generic delegate that defines an input parameter T and a return value TResult.

1
public delegate TResult Func<in T, out TResult>(T arg);
1
2
Func<int, bool> isEven = n => n % 2 == 0;
bool result = isEven(4);

Good Day
😎