Matthew Layton
Matthew Layton

Reputation: 42229

How to store a list of different generic things in C#

Say for example I have this base class

class Mangler<TInput, TOutput>
{
}

And then I make a couple of derived classes

class StringToBytesMangler : Mangler<string, byte[]>
{
}

class IntToGuidMangler : Mangler<int, Guid>
{
}

How do I store a collection of Mangler<TInput, TOutput> where TInput and TOutput may be different at any given time?

i.e.

List<Mangler<?, ?>> list = new List<Mangler<?, ?>>();

list.Add(new StringToBytesMangler());
list.Add(new IntToGuidMangler());

Is this possible?

Upvotes: 2

Views: 2110

Answers (3)

CodingYoshi
CodingYoshi

Reputation: 27009

The whole idea behind generics is to have generic code so type of that class can be treated the same. From what you have posted, it is not easy to see what generic code you have.

Below I have a class that has some generic code:

class Mangler<TInput, TOutput> 
   where TInput: ITInput 
   where TOutput: ITOutput {

   public TInput Input { get; set; }
   public TOutput Output { get; set; }
   public bool IsInputAGuid() {
      if (Guid.Parse(this.Input.SomeThing) == this.Output.SomeGuid ) {
         return true;
      }

      return false;
   }
}

You can see in the above class, when it parses a string to a Guid from this.Input.Something and then it performs == on it with this.Ouput.SomeGuid, the compiler is happy because we have made the constraint that TInput must implement the interface ITInput so the compiler knows this line will work and Input will have Something as a string property:

Guid.Parse(this.Input.SomeThing)

The compiler does not care what the concrete type is so long as Something is available. It is the same idea for TOuput but the compiler expects that it implements ITOutput so it expects a Guid in SomeGuid. This is why the compiler is happy to parse a string to a guid and then perform the == operator on it with another thing which is also a Guid.

Here are the interfaces and some classes which implement them:

internal interface ITInput {
   string SomeThing { get; set; }
}

internal interface ITOutput {
   Guid SomeGuid { get; set; }
}

internal class AnotherInput : ITInput {
   public string SomeThing { get; set; }
}

internal class SomeInput : ITInput {
   public string SomeThing { get; set; }
}

internal class SomeOutput : ITOutput {
   public Guid SomeGuid { get; set; }
}

internal class SomeOtherOutput : ITOutput {
   public Guid SomeGuid { get; set; }
}

Finally, here is the usage where we can treat these generically:

var manglers = new List<Mangler<ITInput, ITOutput>>();

manglers.Add( new Mangler<ITInput, ITOutput>
{ Input = new SomeInput(), Output = new SomeOutput()  } );

manglers.Add( new Mangler<ITInput, ITOutput>
{ Input = new AnotherInput(), Output = new SomeOutput() } );

foreach( var thisMangler in manglers ) {
   var input = thisMangler.Input;
   var output = thisMangler.Output;
   var success = thisMangler.IsInputAGuid();
}

You can see in the foreach regardless of the concrete type, we can call Input, Output and IsInputAGuid() on all of them.

So in your code find what code is generic and then apply the above technique to it. You can either use interfaces or a base class for your constraints.

Upvotes: 0

Codor
Codor

Reputation: 17605

If I understood the question correctly, this is not possible the way you tried it. The types StringToBytesMangler and IntToGuidMangler do not derive from the same type. You could introduce a shared base type, but I recommend reconsidering the design - even if they could be stored in the same collection, they would syntactically have nothing in common (at least it isn't shown in the question).

Upvotes: 2

Thomas Levesque
Thomas Levesque

Reputation: 292405

You need a non-generic Mangler base class.

List<Mangler> list = new List<Mangler>();

list.Add(new StringToBytesMangler());
list.Add(new IntToGuidMangler());

Of course, this means you also need to have non-generic versions of the methods that depend on TInput or TOutput.

Upvotes: 3

Related Questions