A generic delegate has the ability to be assigned by a method that has an unmatched signature to the delegate. We can call this variance in delegates. There are two variances in delegates, and they are covariance and contravariance. Covariance allows a method to have a return type that is more derived (subtype) than the return type that is defined in the delegate. On the other hand, contravariance allows a method to have parameter types that are less derived (supertype) than the parameter types that are defined in the delegate.
The following is an example of covariance in delegates, which we can find in the Covariance.csproj
project. First, we initialize the following delegate:
public partial class Program { private delegate TextWriter CovarianceDelegate(); }
We now have a delegate returning the TextWriter
data type. Then, we also create the StreamWriterMethod()
method returning the StreamWriter
object, which has the following implementation:
public partial class Program { private static StreamWriter StreamWriterMethod() { DirectoryInfo[] arrDirs = new DirectoryInfo(@"C:Windows") .GetDirectories( "s*", SearchOption.TopDirectoryOnly); StreamWriter sw = new StreamWriter( Console.OpenStandardOutput()); foreach (DirectoryInfo dir in arrDirs) { sw.WriteLine(dir.Name); } return sw; } }
We create the StringWriterMethod()
method as well, returning the StringWriter
object with the following implementation:
public partial class Program { private static StringWriter StringWriterMethod() { StringWriter strWriter = new StringWriter(); string[] arrString = new string[]{ "Covariance", "example", "using", "StringWriter", "object" }; foreach (string str in arrString) { strWriter.Write(str); strWriter.Write(' '); } return strWriter; } }
Now, we have two methods returning different objects, StreamWriter
and StringWriter
. The return value data type of these methods is also different, with the CovarianceDelegate
delegate returning the TextWriter
object. However, since StreamWriter
and StringWriter
are derived from the TextWriter
object, we can apply covariance in assigning these two methods to the CovarianceDelegate
delegate.
CovarianceStreamWriterInvoke()
method implementation, which assigns the StreamWriterMethod()
method to the CovarianceDelegate
delegate:
public partial class Program { private static void CovarianceStreamWriterInvoke() { CovarianceDelegate covDelegate; Console.WriteLine( "Invoking CovarianceStreamWriterInvoke method:"); covDelegate = StreamWriterMethod; StreamWriter sw = (StreamWriter)covDelegate(); sw.AutoFlush = true; Console.SetOut(sw); } }
In the StreamWriterMethod()
method, we create StreamWriter
, writing content to the console using the following code:
StreamWriter sw = new StreamWriter( Console.OpenStandardOutput());
Then, in the CovarianceStreamWriterInvoke()
method, we call this code in order to write the content to the console:
sw.AutoFlush = true; Console.SetOut(sw);
If we run the CovarianceStreamWriterInvoke()
method, the following output will be displayed in the console:
From the preceding output console, we serve the list of directories we have inside the Visual Studio 2015 installation path. Indeed, you might have a different list if you installed a different version of Visual Studio.
Now, we are going to utilize the StringWriterMethod()
method to create a CovarianceDelegate
delegate. We create the CovarianceStringWriterInvoke()
method, which has the following implementation:
public partial class Program { private static void CovarianceStringWriterInvoke() { CovarianceDelegate covDelegate; Console.WriteLine( "Invoking CovarianceStringWriterInvoke method:"); covDelegate = StringWriterMethod; StringWriter strW = (StringWriter)covDelegate(); Console.WriteLine(strW.ToString()); } }
We have generated StringWriter
in the StringWriterMethod()
method using the following code:
StringWriter strWriter = new StringWriter(); string[] arrString = new string[]{ // Array of string }; foreach (string str in arrString) { strWriter.Write(str); strWriter.Write(' '); }
Then, we call the following code to write the string to the console:
Console.WriteLine(strW.ToString());
If we run the CovarianceStringWriterInvoke()
method, the string we have defined in the arrString
string array in the StringWriterMethod()
method will be displayed, as follows:
Now, from our discussion on covariance, we have proved the covariance in delegates. The CovarianceDelegate
delegate returning TextWriter
can be assigned to the method returning StreamWriter
and StringWriter
. The following code snippet is taken from several preceding codes to conclude the covariance in delegates:
private delegate TextWriter CovarianceDelegate(); CovarianceDelegate covDelegate; covDelegate = StreamWriterMethod; covDelegate = StringWriterMethod;
Now, let's continue our discussion on variance in delegates by discussing contravariance. The following is the ContravarianceDelegate
delegate declaration, which we can find in the Contravariance.csproj
project:
public partial class Program { private delegate void ContravarianceDelegate(StreamWriter sw); }
The preceding delegate is going to be assigned to the following method, which has the TextWriter
data type parameter, as follows:
public partial class Program { private static void TextWriterMethod(TextWriter tw) { string[] arrString = new string[]{ "Contravariance", "example", "using", "TextWriter", "object" }; tw = new StreamWriter(Console.OpenStandardOutput()); foreach (string str in arrString) { tw.Write(str); tw.Write(' '); } tw.WriteLine(); Console.SetOut(tw); tw.Flush(); } }
The assignment will be as follows:
public partial class Program { private static void ContravarianceTextWriterInvoke() { ContravarianceDelegate contravDelegate = TextWriterMethod; TextWriter tw = null; Console.WriteLine( "Invoking ContravarianceTextWriterInvoke method:"); contravDelegate((StreamWriter)tw); } }
If we run the ContravarianceTextWriterInvoke()
method, the console will display the following output:
From the preceding output, we have successfully assigned a method, taking the TextWriter
parameter to the delegate taking the StreamWriter
parameter. This happens because StreamWriter
is derived from TextWriter
. Let's take a look at the following code snippet:
private delegate void ContravarianceDelegate(StreamWriter sw); private static void TextWriterMethod(TextWriter tw) { // Implementation } ContravarianceDelegate contravDelegate = TextWriterMethod; TextWriter tw = null; contravDelegate((StreamWriter)tw);
The preceding code snippet is taken from the code we discussed in contravariance. Here, we can see that contravDelegate
, a variable typed ContravarianceDelegate
, can be assigned to the TextWriterMethod()
method even though they both have different signatures. This is because StreamWriter
is derived from the TextWriter
object. Since the TextWriterMethod()
method can work with a TextWriter
data type, it will surely be able to work with a StreamWriter
data type as well.