1

I've been trying to learn more about generics, and I finally felt that I ran into a situation that it would be more useful to use it instead of only regular classes. Would the following be a practical use of generics?

A spaceship has hardpoints that can equip only certain types of equipment. The equip function has code to determine if the hardpoint can equip an equipment item based on what the generic parameter T is assigned to.

class Equipment {
...
}

class Thruster : Equipment {
...
}

class Spaceship {
    void Equip<T>(Hardpoint<T> hardpoint, Equipment equipment) {
        hardpoint.Equip(equipment);
    }
}

class Hardpoint<T> where T : Equipment {
    void Equip(Equipment equipment) {
        ...(check if equipment inherits from or is of type T)
    }

    ... (some other code common to all hardpoints)
}

class ThrusterHardpoint : Hardpoint<Thruster> {
    .... (some code specific to thruster hardpoints)
}
  • Why don't you have Equip( T equipment )? You've already constrained T to be an Equipment. ( That's actually the point of making your class generic! ) – Chris Phillips May 7 '14 at 3:33
  • Could you update your question with code on how you expect all this design to be used? – Euphoric May 7 '14 at 5:15
3

I don't like this bit:

void Equip(Equipment equipment) {
    ...(check if equipment inherits from or is of type T)
}

And that's the same thing @MikeSW apparently meant, but didn't elaborate and got downvoted.

If you have generics (reified at that, in C#), why do you leave type-checking until runtime?

The equip function has code to determine if the hardpoint can equip an equipment item

...or you are manually doing the job the type system should be doing for you and you're giving up compile-time type safety. With what good purpose? What justifies that design choice?

  • Good point, thanks. I didn't think of just putting in parameter T instead of Equipment. I felt something could be improved with it, but I didn't know what to do at the time. – Zabytus May 6 '14 at 14:01
  • I'm been thinking about this some more, and I was wondering about the scenario of a dictionary storing various types of equipment, which are loaded from XML files. I won't know at compile time what types those equipment are, so the method for retrieving equipment from the dictionary is: public Equipment GetEquipment(string name) { if (equipmentDictionary.contains(name) return equipmentDictionary[["name"]; } – Zabytus May 7 '14 at 12:37
  • So the semantics of it would be that the dictionary returns a new instance of an equipment item as long as the name exists, so the Equip function has to determine if the item is equippable for the type of item that the client is attempting to equip. I ran out of time to edit my code above, so my apologies for syntax issues. – Zabytus May 7 '14 at 12:47
-2

I don't see any point to your use of the generics in your example. You're also following into a trap with your usage here:

class Thruster : Hardpoint<Thruster>

This is similar to creating a class

class EmployeeList : List<Employee>

There is no reason to do this because List allows you to specify the Employee once only in the usage, not for each separate case. Consider this to make my point. If you had a method that took one of your EmployeeList types as a parameter, but all you had was an IEnumerable you'd have to first build this new type and populate it before you could pass it to your methods. That's extra work for the programmer, compiler, and cpu. This serves me much better.

public static void DoSomething(this IEnumerable<Employee> value) { }

If order matters then it would be.

public static void DoSomething(this IOrderedEnumerable<Employee> value) { }

If indices are needed.

public static void DoSomething(this IList<Employee> value) { }

Now it works for any kind of list, not just your specific EmployeeList type.

So in code you use it like this instead:

var employees = new List<Employee>();
employees.Add(new Employee(1));
// Notice this code doesn't need this extra redundant type EmployeeList at all.

In the above example the generic was useful so that the Add method would have a type it expects. You specified the type when you declared the instance using its default constructor. So basically this saves you from having to cast your objects into something, and also so the intellisense and the compiler can make sure you are indeed passing Employee instances into the Add method of an instance of List.

Before generics existed you had to do this instead for the previous example:

var employees = new List();
employees.Add(new Employee(1));
employees.Add(new Car(2)); // Compiler won't see this as an error, because a Car is an object.
Console.WriteLine(((Employee)employees[0]).EmployeeID); // Have to case anything coming out of the List because compiler thinks they are just objects, not Employee instances.

As you can see the old way would let you do a bad thing, add Car instances to a list of Employee's. It also required the programmer to type more and cast everything manually. It would blow up in this case if you did this:

Console.WriteLine(((Employee)employees[1]).EmployeeID); // Subset item 1 is not an instance of Employee, cast will fail.

EDIT: Added explanation of why class EmployeeList is extra and there are better solutions to domain specific set based operations.

  • 1
    I don't think those cases are anywhere near same. For example having EmployeeCollection : Collection<Employee> is great way to add domain behavior to collection of employees. – Euphoric May 7 '14 at 5:06
  • Its not like his code because as I stated his code is doing nothing with the generics. I was offering up an example of when they add value. – Rob May 7 '14 at 19:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.