Profilo di StevenDevelopmentalBlogElenchi Strumenti Guida

Blog


01 giugno

Advanced Generics

I've recently been reading this book by Jon Skeet about C# 2 and 3, and it got me all inspired to write about generics. Jon's great; he's extremely active in the C# and Java community, and recently responded to an email question I had in less than 10 minutes! I'm finding the book insightful and useful and greatly enjoying my bus rides to work when I get to read it.

This post is going to assume you are familiar with using generic classes like List<T> and Nullable<T>. So lets start with the syntax for declaring our own generic class:

public class Monkey { }
public class Animator<T> {
    private T instance;
    public Animator(T t)
    {
        instance = t;
    }
    public void MoveOnce() { }
}
Animator<Monkey> animator = new Animator<Monkey>(new Monkey());
animator.Move();

This is pretty simple: the Animator class accepts a generic type T and requires you pass a T in its constructor. The MoveOnce() method is empty at the moment, we'll get to that later.

Currently our generic Animator class is somewhat type-safe: we can only pass instances of T to its constructor, but we don't really know much about T. We'd like to be able to move our instances (such as a monkey) so we need to define some things about what kinds of T we are willing to accept. Lets go back to our Monkey class for a moment and define an interface for it:

public interface IMoveable {
    void Move();
    void Stop();
}
public class Monkey : IMoveable {
    public void Move() { }
    public void Stop() { }
}

Great! We've decided that the fact a Monkey can move and stop is pretty general, and is probably something other animals can do, so we've declared an interface to indicate our monkey has those capabilities. We might also define IEater, or IDungFlinger. Now we can revisit our generic type in our Animator class definition as follows:

public class Animator<T> where T : IMoveable
{
    private T instance;
    public Animator(T t)
    {
        instance = t;
    }
    public void MoveOnce() {
        instance.Move();
        instance.Stop();
    }
}

This is where the magic begins. We've placed a constraint on our generic type, stating that it will only ever accept a type that implements our IMoveable interface. If we try to use the Animator class with something else, we will get a compile-time exception. Even better yet is that our development environment knows that since our instance will always implement IMoveable, it therefore must have a Move() and Stop() method, therefore we can call it anywhere in our class; in this case the MoveOnce() method.

You can declare more than one generic type in your class definitions. Imaging a class that facilitates a race for monkeys, whether they be chimps, gorillas, or marmosets. Consider this generic class declaration:

public class Race<T, U> where T : Monkey where U : Monkey {
    public Race(T monkey1, U monkey2) { }
    public void RunRace() { }
}
public class Marmoset : Monkey { }
public class Chimp : Monkey { }

Here we have defined that our class accepts 2 generic types: T and U. We have further restricted them that they both have to inherit from the Monkey class. Here's some example calls using this new class:

Race<Chimp, Marmoset> race1 =
      new Race<Chimp, Marmoset>(new Chimp(), new Marmoset());
Race<Monkey, Chimp> race2 =
      new Race<Monkey, Chimp>(new Monkey(), new Chimp());
Race<Marmoset, Marmoset> race3 =
      new Race<Marmoset, Marmoset>(new Marmoset(), new Marmoset());

As you can see that anything that inherits Monkey is valid. Of interest, the second example shows that even an instance of Monkey is valid. This shows that the where clause in our type restriction means anything that inherits from X or is X.

Finally here are a couple of extra cool things you can do with generic type restrictions as general non-monkey related examples:

public class TestCase1<T> where T : class { }
public class TestCase2<T> where T : struct { }
public class TestCase3<T> where T : IDisposable, IMoveable, IComparable { }
public class TestCase4<T, U> where T : U { }
public class TestCase5<T, U> where T : U, IMoveable { }
public class TestCase6<T, U> where T : class where U : struct{ }
public class TestCase7<T, U> where T : class where U : struct, T { }

TestCase1 demonstrates that we can force the type to be a reference type.
Likewise TestCase2 ensures that the type must be a value type. How is this useful? Well consider a generic class that performs expensive operations such as copying from one list to another. Reference types are just pointers and therefore inexpensive to copy around, thus ensuring only reference types are allowed in our class could be quite useful.

TestCase3 shows that we can ensure T implements multiple interfaces.
TestCase4 shows that one generic type can be forced to inherit from another. An example of an implementation might be:

TestCase4<Chimp, Monkey> test = new TestCase4<Chimp, Monkey>();

This is valid because Chimp inherits from Monkey.
TestCase5 shows that a type can be restricted to inherit as well as implement interfaces.
TestCase6 demonstrates that one type must be a reference type while the other can be a value type.

The last TestCase7 is somewhat interesting. Its declaration indicates that T is a reference type, and U is a value type that must inherit from T. How can this be? It essentially states that you must pass a struct that inherits from a class. But C# won't let you declare this.

To be honest at first I didn't know the answer to this. Thus the email to Jon, and his explanation was simple and it made sense: interfaces are reference types. Hopefully you are aware that structs can implement interfaces also (but can't do inheritance), so we might define a new animal as follows:

public struct Mouse : IMoveable {
    public void Move() { }
    public void Stop() { }
}

Then we can legitimately use the TestCase7 syntax:

TestCase7<IMoveable, Mouse> test = new TestCase7<IMoveable, Mouse>();

Then of course, valid values for an IMoveable are a Chimp, Marmoset, or even another Mouse, while of course you can only use a Mouse for the second type.

Hopefully this has given you a better idea of what you can do with generics.

Commenti

Attendere...
Il commento immesso è troppo lungo. Immetti un commento più breve.
Immissione non effettuata. Riprova.
Impossibile aggiungere il commento al momento. Riprova più tardi.
Per aggiungere un commento è necessaria l'autorizzazione di un genitore. Chiedi autorizzazione
I tuoi genitori hanno disattivato i commenti.
Impossibile eliminare il commento al momento. Riprova più tardi.
Hai raggiunto il numero massimo di commenti pubblicabili giornalmente. Riprova tra 24 ore.
Impossibile lasciare commenti. La funzionalità è stata disattivata perché i sistemi hanno rilevato una possibile attività di spamming dal tuo account. Se ritieni che il tuo account è stato disattivato per errore, contatta il supporto tecnico di Windows Live.
Esegui il seguente controllo di protezione per completare la pubblicazione del commento.
I caratteri digitati nel controllo di protezione devono corrispondere ai caratteri dell'immagine o della riproduzione audio.

Per aggiungere un commento, accedi con il tuo Windows Live ID (se utilizzi Hotmail, Messenger o Xbox LIVE possiedi già un Windows Live ID). Accedi


Non hai ancora un Windows Live ID? Registrati

Riferimenti

L'URL di riferimento per questo intervento è:
http://stevennagy.spaces.live.com/blog/cns!B2EFDBF0964586B3!232.trak
Blog che fanno riferimento a questo intervento
  • Nessuno