开发者

Understanding C# generics much better

开发者 https://www.devze.com 2023-01-14 11:37 出处:网络
I looked at some sample code using C# generics. Why and when should I use them? All the examples were complex. I nee开发者_如何学God a simple, clear example that gets me started with C# generics.A ve

I looked at some sample code using C# generics. Why and when should I use them?

All the examples were complex. I nee开发者_如何学God a simple, clear example that gets me started with C# generics.


A very simple example is the generic List<T> class. It can hold a number of objects of any type. For example, you can declare a list of strings (new List<string>()) or a list of Animals (new List<Animal>()), because it is generic.

What if you couldn't use generics? You could use the ArrayList class, but the downside is that it's containing type is an object. So when you'd iterate over the list, you'd have to cast every item to its correct type (either string or Animal) which is more code and has a performance penalty. Plus, since an ArrayList holds objects, it isn't type-safe. You could still add an Animal to an ArrayList of strings:

ArrayList arrayList = new ArrayList();
arrayList.Add(new Animal());
arrayList.Add("");

So when iterating an ArrayList you'd have to check the type to make sure the instance is of a specific type, which results in poor code:

foreach (object o in arrayList)
{
  if(o is Animal)
    ((Animal)o).Speak();
}

With a generic List<string>, this is simply not possible:

List<string> stringList = new List<String>();
stringList.Add("Hello");
stringList.Add("Second String");
stringList.Add(new Animal()); // error! Animal cannot be cast to a string


To summarize other answers with some emphasis:

1) generics enable you to write 'generic' code (i.e., it will work for multiple types). If you have 'generic' behavior you want to write, which you need to behave for differing data types, you only need to write that code once. The example of List is a great example, you can need lists of perhaps customers, products, orders, suppliers...all using the same code instantiated for each type

//  snippet
List<Customer> customers = new List<Customer>();
Customer thisCustomer = new Customer();
customers.Add(thisCustomer);

List<Order> orders = new List<Order>();
Order thatOrder = new Order();
orders.Add(thatOrder);

//  etc.

2) amazingly, generics still enable type safety! So if you try this, you will rightly get an error:

//  continued for snippet above
Order anotherOrder = new Order();
customers.Add(anotherOrder);    //  FAIL!

And you would want that to be an error, so that later on your customer processing code doesn't have to handle a bogus order showing up in the customers list.


Duplication is the root of all evil. One case of code duplication occurs when you have to perform same operation on different types of data. Generics let you avoid it by allowing you to code around a 'generic' type and later substitute it with specific types.

The other solution to this problem is to use variables of type 'System.Object' to which object of any type can be assigned. This method involves boxing and unboxing operations between value and reference types which hit performance. Also type casting keeps the code from being clean.

Generics are supported in MSIL and the CLR which makes it perform really well.

You should read these articles about generics -

http://msdn.microsoft.com/en-us/library/512aeb7t(VS.80).aspx

http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx#csharp_generics_topic1


In a nutshell, generics allow you to write classes that work with objects of any type, but without having to cast the data to Object. There are performance benefits for this, but it also makes your code more readable, maintainable, and less error-prone.

You should always use generics as opposed to the .NET 1.1 style classes when possible.


I have a use case here from Matt Milner, instructor at LinkedIn Learning. It's a little verbose, not simple as you requested, but I found it useful for diving deeper on why generics are necessary.

Generics Over Value and Reference Types (on C#)

  • Value Types: bool, byte, char, decimal, double, enum, float, int, long, sbyte, short, struct, uint, ulong, ushort.
  • Reference Types: String, Arrays (even if their elements are value types), Class, Delegate.

Part 1: On Value Types

Let's say you have this method:

static void Swap(object first, object second)
{
    object temp = second;
    second = first;
    first = temp;
}

And you use it like so:

int x = 5, y = 7;
Swap(x, y);
System.Console.WriteLine($"X: {x} and Y: {y}");

Which prints out:

X: 5 and Y: 7

No swap here. Why? Because:

  • int are value types.
  • So, when they go into the method, and out, they have different scope. They've been just "copied by value" and not "by reference".
  • Note: C# allows treating int types as objects, but this involves doing boxing and unboxing. Keep in mind this is an expensive operation at memory level.

Should this problem be solved by using reference types? Let's try it out.

Part 2: On Reference Types

We are going to use a custom class, previously defined in some library:

var p1 = new Person
{
    FirstName = "Matt",
    LastName = "Milner"
};

var p2 = new Person
{
    FirstName = "Amanda",
    LastName = "Owner"
};

Let's swap them.

Swap(p1, p2);
System.Console.WriteLine($"Person 1 is: {p1.FirstName}");

We should read, Amanda, but we get instead:

Person 1 is: Matt

Why? Usually we would expect the change to occur, since class instances are passed by reference. But this is not the case, because we are in fact passing a copy of the address to the temp instance.

What if we modify the method with ref?

Part 3: Using ref?

static void Swap(ref object first, ref object second)
{
    object temp = second;
    second = first;
    first = temp;
}

This should allow us to change not just the parts of the object, but also what they point to.

Swap(ref p1, ref p2);
System.Console.WriteLine($"Person 1 is: {p1.FirstName}");
  • This won't work. Why?
  • Because you can't convert from a ref of Person to a ref of Object. They're too unlike types.
  • You'll read a compilation error from the compiler before even running the program.

Part 4: Using Generics

  • This problem is solved with generics.
  • They can be used with any given type (int, arrays, etc) without stepping in into these problems.
static void Swap<T>(ref T first, ref T second)
{
    T temp = second;
    second = first;
    first = temp;
}
  • Here we have a T parameter, generic or "Type" parameter.
  • T replaces completely the Object declarations in the method, and allows to tell to the method what we are putting in it when it is used. Like so:
Swap<Person>(ref p1, ref p2);
Swap<int>(ref x, ref y);

System.Console.WriteLine($"Person 1: {p1.FirstName}");
System.Console.WriteLine($"X: {x} and Y: {y}");

Which prints out:

Person 1 is: Amanda
X: 7 and Y: 5

Summing Up

  • For both reference and value types:
static void Swap(object first, object second); // No swap
static void Swap(ref object first, ref object second); // No swap
static void Swap<T>(ref T first, ref T second); // Swap
  • This specifies dynamically the type for the method.
  • At compile time, the calling code provides/informs the type is being used.

Note: I still need to check myself why the reference type behave this way, which as far as I know it is related to how object memory addresses are passed, referenced and copied. For the most part I just followed along the explanation from Matt Miller.

0

精彩评论

暂无评论...
验证码 换一张
取 消