Generic Interface with a type constraint in C#

In a previous blog I discussed how to use and implement an interface with generics. I decided shortly after that it would be a good idea, in my context, to loosely bind a type to the interface. In the previous blog, it was left 100% generic and that was fine for the example.

However, there may be a time when you wanted to “tighten the ropes” a little. Therefore, I modified the IGuitar interface to implement a constraint.

public interface IGuitar<t> where T : GuitarBase
{
     string Name { get; set; }
     string GetType(T t);
}

Notice that the code requires that T be of type GuitarBase which is my guitar base class. This means that this class and any derived class can utilize this interface. Below are 3 classes. GuitarBase, GuitarBaseExtended, which is derived from GuitarBase and GuitarBaseOptional, which is not derived from GuitarBase or GuitarBaseExtended. This means that when we implement the GuitarBaseExtended or a derived class, we can use the IGuitar interface.

public class GuitarBase
  {
      public string Builder { get; set; }
      public DateTime GetBuildDate()
      {
          return DateTime.Now;
      }
  }
 
  public class GuitarBaseExtended : GuitarBase
  {
      public int Cost { get; set; }
      public int GetSoldFor()
      {
          Random random = new Random();
          int RandomNumber = random.Next(2, 5);
 
          return RandomNumber * Cost;
      }
  }
 
  public class GuitarBaseOptional
  {
      public int NumberOfStrings { get; set; }
  }

However, if we try to implement the IGuitar interface using the GuitarBaseOptional , we will get a compile time error. The below implementation will result in a compile time error because GuitarBaseOptional and therefore SteelGuitar are not derived from the GuitarBase class.

public class SteelGuitar : GuitarBaseOptional,
                            IGuitar<SteelGuitar>
{
     public string Name { get; set; }
     public string GetType(ClassicalGuitar t)
     {
         return "The classical guitar is: " + t.Name;
     }
}

The below implementation or any implementation derived from it, will work as we expect.

public class ClassicalGuitar : GuitarBaseExtended,
                                IGuitar<ClassicalGuitar>
{
     public string Name { get; set; }
     public string GetType(ClassicalGuitar t)
     {
         return "The classical guitar is: " + t.Name;
     }
}

Below is an example of how in our program we implement the ClassicalGuitar class. The cGuitar.Name property and the GetType() method are required due to the implementation of the IGuitar interface. The cGuitar.Builder property and the cGuitar.GetBuildDate() method are actioned by the GuitarBase class. And finally, the cGuitar.Cost and cGuitar.GetSoldFor() method are actioned by the GuitarBaseExtended class.

ClassicalGuitar cGuitar = new ClassicalGuitar();
cGuitar.Name = "C132S";
Console.WriteLine(cGuitar.GetType(cGuitar));
cGuitar.Builder = "Takamine";
Console.WriteLine("The builder is :" + cGuitar.Builder);
Console.WriteLine("It was built on :" +
                   cGuitar.GetBuildDate());
cGuitar.Cost = 350;
Console.WriteLine("It sold for :" +
                    cGuitar.GetSoldFor().ToString());

Download the source