Generic interfaces work in much the same way as the previous examples in generics. Let's assume that we want to find the properties of certain classes in our code, but we can't be sure how many classes we will need to inspect. A generic interface could come in very handy here.
We need to inspect several classes for their properties. To do this, we will create a generic interface that will return a list of all the properties found for a class as a list of strings.
Let's take a look at the following implementation of the generic interface as follows:
IListClassProperties<T>
. The interface will define a method that needs to be used called GetPropertyList()
that simply uses a LINQ query to return a List<string>
object:interface IListClassProperties<T> { List<string> GetPropertyList(); }
InspectClass<T>
. Let the generic class implement the IListClassProperties<T>
interface created in the previous step:public class InspectClass<T> : IListClassProperties<T> { }
GetPropertyList()
has not been implemented in the InspectClass<T>
generic class:GetPropertyList()
method in your InspectClass<T>
class without any implementation. You will add the implementation in a moment. If you try to run your code without adding any implementation to the GetpropertyList()
method, the compiler will throw NotImplementedException
:public class InspectClass<T> : IListClassProperties<T> { public List<string> GetPropertyList() { throw new NotImplementedException(); } }
InspectClass<T>
class that takes a generic type parameter and sets it equal to the private variable _classToInspect
that you also need to create. This is setting up the code that we will use to instantiate the InspectClass<T>
object. We will pass to the object we need a list of properties from the constructor, and the constructor will set the private variable _classToInspect
so that we can use it in our GetPropertyList()
method implementation:public class InspectClass<T> : IListClassProperties<T> { T _classToInspect; public InspectClass(T classToInspect) { _classToInspect = classToInspect; } public List<string> GetPropertyList() { throw new NotImplementedException(); } }
GetPropertyList()
method. It is here that the LINQ query will be used to return a List<string>
object of all the properties contained in the class supplied to the constructor:public List<string> GetPropertyList() { return _classToInspect.GetType().GetProperties().Select(p => p.Name).ToList(); }
Invoice
. This is one of several classes that can be used in the system, and the Invoice
class is one of the smaller classes. It usually just holds invoice data specific to a record in the invoices records of the data store you connect to. We need to find a list of the properties in this class:public class Invoice { public int ID { get; set; } public decimal TotalValue { get; set; } public int LineNumber { get; set; } public string StockItem { get; set; } public decimal ItemPrice { get; set; } public int Qty { get; set; } }
InspectClass<T>
generic class that implements the IListClassProperties<T>
generic interface. To do this, we will create a new instance of the Invoice
class. We will then instantiate the InspectClass<T>
class, passing the type in the angle brackets and the oInvoice
object to the constructor. We are now ready to call the GetPropertyList()
method. The result is returned to a List<string>
object called lstProps
. We can then run foreach
on the list, writing the value of each property
variable to the console window:Invoice oInvoice = new Invoice(); InspectClass<Invoice> oClassInspector = new InspectClass<Invoice>(oInvoice); List<string> lstProps = oClassInspector.GetPropertyList(); foreach(string property in lstProps) { Console.WriteLine(property); } Console.ReadLine();
Invoice
class:As you can see, the properties are listed as they exist in the Invoice
class. The IListClassProperties<T>
generic interface and the InspectClass<T>
class don't care what type of class they need to inspect. They will take any class, run the code on it, and produce a result.
But the preceding implementation still poses a slight problem. Let's have a look at one of the variation of this problem:
InspectClass<int> oClassInspector = new InspectClass<int>(10); List<string> lstProps = oClassInspector.GetPropertyList(); foreach (string property in lstProps) { Console.WriteLine(property); } Console.ReadLine();
You can see that we have easily passed an integer value and type to the InspectClass<T>
class, and the code does not show any warnings at all. In fact, if you ran this code, nothing would be returned and nothing outputs to the console window. What we need to do is implement the constraints on our generic class and interface.
where T : class
clause. The code now needs to look like this:public class InspectClass<T> : IListClassProperties<T> where T : class { T _classToInspect; public InspectClass(T classToInspect) { _classToInspect = classToInspect; } public List<string> GetPropertyList() { return _classToInspect.GetType().GetProperties().Select(p => p.Name).ToList(); } }
int
type passed to the InspectClass<T>
class:The reason for this is because we have defined a constraint against our generic class and interface. We have told the compiler that we only accept reference types. Therefore, this applies to any class, interface array, type, or delegate. Our Invoice
class will therefore be a valid type, and the constraint will not apply to it.
We can also be more specific in our type parameter constraints. The reason for this is that we perhaps do not want to constrain the parameters to reference types. If we, for example, wanted to button down the generic class and interface to only accept classes created inside our current system, we can implement a constraint that the argument for T
needs to be derived from a specific object. Here, we can use abstract classes again:
AcmeObject
and specify that all classes that inherit from AcmeObject
implement a property called ID
:public abstract class AcmeObject { public abstract int ID { get; set; } }
AcmeObject
. To apply the constraint, modify the generic class and place the where T : AcmeObject
constraint after the interface implementation. Your code should now look like this:public class InspectClass<T> : IListClassProperties<T> where T : AcmeObject { T _classToInspect; public InspectClass(T classToInspect) { _classToInspect = classToInspect; } public List<string> GetPropertyList() { return _classToInspect.GetType().GetProperties().Select(p => p.Name).ToList(); } }
Invoice
class to inherit from the AcmeObject
abstract class. Implement the ID
property as defined in the abstract class:public class Invoice : AcmeObject { public override int ID { get; set; } public decimal TotalValue { get; set; } public int LineNumber { get; set; } public string StockItem { get; set; } public decimal ItemPrice { get; set; } public int Qty { get; set; } }
SalesOrder
and CreditNote
. This time, however, only make the SalesOrder
class inherit from AcmeObject
. Leave the CreditNote
object as is. This is so that we can clearly see how the constraint can be applied:public class SalesOrder : AcmeObject { public override int ID { get; set; } public decimal TotalValue { get; set; } public int LineNumber { get; set; } public string StockItem { get; set; } public decimal ItemPrice { get; set; } public int Qty { get; set; } } public class CreditNote { public int ID { get; set; } public decimal TotalValue { get; set; } public int LineNumber { get; set; } public string StockItem { get; set; } public decimal ItemPrice { get; set; } public int Qty { get; set; } }
Invoice
and SalesOrder
classes. The code is straightforward, and we can see that Visual Studio does not complain about either of these two classes:Invoice oInvoice = new Invoice(); InspectClass<Invoice> oInvClassInspector = new InspectClass<Invoice>(oInvoice); List<string> invProps = oInvClassInspector.GetPropertyList(); foreach (string property in invProps) { Console.WriteLine(property); } Console.ReadLine(); SalesOrder oSalesOrder = new SalesOrder(); InspectClass<SalesOrder> oSoClassInspector = new InspectClass<SalesOrder>(oSalesOrder); List<string> soProps = oSoClassInspector.GetPropertyList(); foreach (string property in soProps) { Console.WriteLine(property); } Console.ReadLine();
CreditNote
class, we will see that Visual Studio will warn us that we can't pass the CreditNote
class to the InspectClass<T>
class because the constraint we implemented only accepts objects that derive from our AcmeObject
abstract class. By doing this, we have effectively taken control over exactly what we allow to be passed to our generic class and interface by means of constraints:Speaking of generic interfaces, we have seen that we can implement behavior on a generic class by implementing a generic interface. The power of using the generic class and generic interface is well illustrated earlier.
Having said that, we do believe that knowing when to use constraints is also important so that you can close down your generic classes to only accept the specific types that you want. This ensures that you don't get any surprises when someone accidently passes an integer to your generic class.
Finally, the constraints that you can use are as follows:
where T: struct
: The type argument must be any value typeswhere T: class
: The type argument must be any reference typeswhere T: new()
: The type argument needs to have a parameterless constructorwhere T: <base class name>
: The type argument must derive from the given base classwhere T: <T must derive from object>
: T
The type argument must derive from the object after the colonwhere T: <interface>
: The type argument must implement the interface specified18.118.12.186