Changing a Type within a Class when you Instantiate: An Intro to Generics

Have you ever run into a problem that you couldn’t describe well enough to even ask about it?

“I want to change a specific type to another type when I instantiate a class to make it more general and reusable…”

“Is this even possible?”

Say you’re designing a multidimensional Dictionary that holds geographic AreaObjects

public class AreaObjectCollection {

    private Dictionary<int, Dictionary<int, List<AreaObject>>> _map = 
        new Dictionary<int, Dictionary<int, List<AreaObject>>>();

    public void Add(AreaId id, AreaObject obj) {
        int x = id.X;
        int y = id.Y;
        if (!_map.ContainsKey(x)) {
            _map.Add(x, new Dictionary<int, List<AreaObject>>());
        }
        if (!_map[x].ContainsKey(y)) {
            _map[x].Add(y, new List<AreaObject>());
        }
        _map[x][y].Add(obj);
    }
    ...
}

public class AreaId {
    public int X { get; set; }
    public int Y { get; set; }
}
public class AreaObject {
    public string AreaId { get; set; }
}

What if you wanted to be able to swap out AreaObject with a different type on instantiation?

AreaObjectCollection<SomeType> x = new AreaObjectCollection<SomeType>();

You would need all the AreaObject declarations in the code to become SomeType when you instantiate the AreaObjectCollection class.

Generics

We can use Generics for this. I spent a long time never writing my own classes that used Generic syntax. This was fine, for the most part… then, one of those problems came up that I couldn’t describe very well. As a beginner, I didn’t know that a language feature like Generics was built just for me!

“Generics introduce to the .NET Framework the concept of type parameters, which make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code”

MSDN

So there you go! I wanted to: *change a specific type to another type when I instantiate a class to make it more general and reusable…*

Let’s see what we need to change with our original code to make this happen…

Quick Refactor

To make our AreaObject declarations swappable when we instantiate AreaObjectCollection, we give the class a generic type parameter:

public class GenericAreaObjectCollection<T> where T : IAreaObject  {

    public Dictionary<int, Dictionary<int, List<T>>> map =
        new Dictionary<int, Dictionary<int, List<T>>>();
 
    public void Add(AreaId id, T obj) {
        int x = id.X;
        int y = id.Y;
        if (!map.ContainsKey(x)) {
            map.Add(x, new Dictionary<int, List<T>>());
        }
        if (!map[x].ContainsKey(y)) {
            map[x].Add(y, new List<T>());
        }
        map[x][y].Add(obj);
    }
}

public class AreaObject : IAreaObject {
    public string AreaId { get; set; }
}
public interface IAreaObject {
    string AreaId { get; set; }
}

We’ve done two Generic things here:

  1. On line 1, we added a generic type parameter with GenericAreaObjectCollection<T>. This allows us to pass in the type that we want to substitute for <T>
  2. On line 1, we added a generic type parameter constraint with where T : IAreaObject. This makes sure we can only pass in a type that implements IAreaObject interface. On line 22, we make sure AreaObject implements IAreaObject

We can now replace AreaObject with a different class that implements IAreaObject

[Fact]
public void GenericAreaObjectCollection_Add_enhancedAreaObject() {
    var areaObjectCollection = new GenericAreaObjectCollection<EnhancedAreaObject>();
    areaObjectCollection.Add(
        new AreaId() { X = 1, Y = 1 },
        new EnhancedAreaObject() {
            AreaId = "My Area",
            SomeCoolAttribute = "I'm a better AreaObject"
        });

    _output.WriteLine(areaObjectCollection.ToJson());
    /*
      "map": {
        "1": {
          "1": [
            {
              "AreaId": "My Area",
              "SomeCoolAttribute": "I'm a better AreaObject"
            }
          ]
        }
    */
}

Summary

We had a problem that wasn’t easy to connect a solution to. We simply wanted design a class that operated on a specific type declared within it. We wanted to extend the class so that we could also operate on other types - swapping out that specific type and all of its declarations when we instantiate it.

This is a job for Generics. By designing the class with a generic type parameter <T> for the swappable type, we are able to instantiate the class with any type that satisfies the generic type parameter constraint specified - where T :.

Generics are probably not new to you, but applying Generics when you run into a prime situation for them might be. Generics is a deep subject, e.g. covariance and contravariance. I hope to get back to it soon, so be sure to sign up for my newsletter, leave a comment, or just get in touch somehow!


Sources

Tweet
comments powered by Disqus