Introduction to generics in C# Part 5
March 8, 2016 1 Comment
Introduction
In the previous post we looked at how to declare multiple type parameters. We saw that it was a very simple thing to do. We just add as many parameters as we need and separate them by a comma. It’s perfectly fine to refer to those parameters later on in the class level functions. It’s equally fine to put multiple parameters on the function level as well if you don’t want to make the entire class generic.
In this post we’ll take a quick look at parameter type constraints.
Constraints
So far we’ve only seen examples where we could declare just any type of parameter type, like here:
public interface IConfigurationRepository { T ReadConfigurationValue<T>(string key, T defaultValue, bool throwException = false); }
We can potentially pass in just any object type for ‘T’: a string, a Customer, a GUID, etc. However, there are occasions where we want to be able to constrain the types that the generic method or class can handle.
Consider a situation where you’d like to print the properties of an object. There are various ways to implement a simple properties printer but we’ll look at a – somewhat artificial – solution where we can demonstrate the subject of this post. The object that needs printing must implement the following interface:
public interface IPrintable { IDictionary<string, string> GetProperties(); }
So the object needs to expose its properties in a simple string dictionary. Let’s revisit the Customer object we saw before in this series. The Customer class implements the interface as follows:
public class Customer : Domain<int>, IPrintable { public Customer(int id, string name) : base(id) { Name = name; } public string Name { get; } public IDictionary<string, string> GetProperties() { IDictionary<string, string> props = new Dictionary<string, string>(); props["Name"] = Name; props["Id"] = Convert.ToString(Id); props["ObjectType"] = "Customer"; return props; } }
The next step is to decide what format we want to follow: XML, JSON, CSV or some other, possibly custom format. There are multiple printer implementations so we should create an interface. However, we want to make sure that only those objects can use this interface that implement the IPrintable interface. We want to do that since we want to make sure that there will be GetProperties() method for the generic type T.
That’s an example of a constraint. We create a generic type but we limit the usage of the type to those objects that fulfil some criteria: they implement an interface, they derive from a certain class and the like. The ‘where’ keyword is used for this purpose. Here’s the IPropertiesPrinter interface:
public interface IPropertiesPrinter<T> where T : IPrintable { string Print(T toBePrinted); }
The where clause means that type T must implement the IPrintable interface otherwise the Print method cannot be called on it.
Let’s see two implementations, one for JSON and one for XML:
public class JsonPropertiesPrinter<T> : IPropertiesPrinter<T> where T : IPrintable { public string Print(T toBePrinted) { string ret = JsonConvert.SerializeObject(toBePrinted.GetProperties()); return ret; } } public class XmlPropertiesPrinter<T> : IPropertiesPrinter<T> where T : IPrintable { public string Print(T toBePrinted) { string root = typeof(T).ToString(); XDocument xDocument = new XDocument ( new XDeclaration("1.0", "utf-8", null), new XElement(root, toBePrinted.GetProperties().Select(kvp => new XElement(kvp.Key, kvp.Value))) ); string ret = xDocument.ToString(); return ret; } }
Let’s see how we can call these implementations:
Customer customer = new Customer(123, "Great customer"); IPropertiesPrinter<Customer> printer = new JsonPropertiesPrinter<Customer>(); string printed = printer.Print(customer);
The variable ‘printed’ will be…:
{"Name":"Great customer","Id":"123","ObjectType":"Customer"}
Whereas the XML printer…
Customer customer = new Customer(123, "Great customer"); IPropertiesPrinter<Customer> printer = new XmlPropertiesPrinter<Customer>(); string printed = printer.Print(customer);
…produces the following output:
<Various.Generics.Customer> <Name>Great customer</Name> <Id>123</Id> <ObjectType>Customer</ObjectType> </Various.Generics.Customer>
Attempting to use the IPropertiesPrinter interface with an object that doesn’t implement the IPrintable interface will result in a compiler error.
You can find out more about the options for the where clause in this MSDN article.
Read the next post here which also concludes this series.
View all various C# language feature related posts here.
very good sample use-cases. -) thx to ur mother!