Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issue with generics, interfaces, and casting

I recently added an interface to some custom user controls I have implemented. The interface is pretty basic. It has one method that supports chaining:

Public Interface IMyInterface(Of T As WebControl)
    Function DoSomething() As T
End Interface

The implementations are also pretty basic:

Public Class MyCustomControl
    Inherits CompositeControl
    Implements IMyInterface(Of MyCustomControl)

Public Function DoSomething() As MyCustomControl _
    Implements IMyInterface(Of MyCustomControl).DoSomething
    ' do stuff

    Return Me
End Class

Everything works fine up to this point. The issues arise when I attempt to loop over a collection of controls that all implement the IMyInterface interface, like so:

Dim myList = New List(Of IMyInterface(Of WebControl))

myList.Add(someCustomControl)

myList.ForEach(Sub(i) i.DoSomething())

someCustomControl is a MyCustomControl which implements IMyInterface(Of MyCustomControl) instead of IMyInterface(Of WebControl).

I am getting this error on the second line (where I try to add someCustomControl):

Option Strict On disallows implicit conversions from 'MyCustomControl' to 'IMyInterface(Of WebControl)'.

Is there any way to get around this error? I am close to having it working but I do not know enough about generics to get beyond this point.

like image 579
jbabey Avatar asked Jun 10 '13 19:06

jbabey


People also ask

Does generics eliminate the use of casting?

As mentioned previously, generics can eliminate the requirement for casting.

Do generics prevent type cast errors?

Implementing generics into your code can greatly improve its overall quality by preventing unprecedented runtime errors involving data types and typecasting.

What is generic interface?

A generic interface is primarily a normal interface like any other. It can be used to declare a variable but assigned the appropriate class. It can be returned from a method. It can be passed as argument. You pass a generic interface primarily the same way you would an interface.

What are the restrictions on generics?

Cannot Use Casts or instanceof With Parameterized Types. Cannot Create Arrays of Parameterized Types. Cannot Create, Catch, or Throw Objects of Parameterized Types. Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type.


2 Answers

Covariance is a language feature that was introduced in VS 2010, and solves your problem. You need to define your generic such that the type T has the Out keyword in front of it:

Public Interface IMyInterface(Of Out T As WebControl)
    Function DoSomething() As T
End Interface

When you use the Out keyword, you are using covariance. It allows generics of a more derived type to be used in place of a generic with the base type. So in your case it will allow a IMyInterface(Of MyCustomControl)) object in places where the code would normally expect IMyInterface(Of WebControl)), such as your for loop.

Note that covariance has a restriction. The covariant type T can only be used as a function return value, and not as a parameter into a function (or sub). For example, if the DoSomething signature in IMyInterface looked like this the compiler would complain:

' Here the type T is used as an input param - compiler error
Sub DoSomething(ByVal sampleArg As T)

Given your chaining scenario, I don't think the above restriction is a problem.

More Info at MSDN:

  • Covariance and Contravariance
  • Creating Variant Generic Interfaces
like image 154
chue x Avatar answered Sep 28 '22 23:09

chue x


I don't know what your function DoSomething does, but I try assigning the instance's CssClass in there for testing purpose.

Declare the interface as follows:

Public Interface IMyInterface(Of Out T As WebControl)
    Function DoSomething() As T
End Interface

Notice the Out T parameter.

Create 2 controls that implement the interface:

Public Class MyCustomControl1
    Inherits CompositeControl
    Implements IMyInterface(Of MyCustomControl1)

    Public Function DoSomething() As MyCustomControl1 Implements IMyInterface(Of MyCustomControl1).DoSomething
        ' do stuff
        Me.CssClass = "XXX"
        Return Me
    End Function

End Class

Public Class MyCustomControl2
    Inherits CompositeControl
    Implements IMyInterface(Of MyCustomControl2)

    Public Function DoSomething() As MyCustomControl2 Implements IMyInterface(Of MyCustomControl2).DoSomething
        ' do stuff
        Me.CssClass = "YYY"
        Return Me
    End Function

End Class

On a test page's PageLoad event:

Dim someCustomControl As New MyCustomControl1
Dim someCustomControl2 As New MyCustomControl2

Dim myList = New List(Of IMyInterface(Of WebControl))

myList.Add(someCustomControl)
myList.Add(someCustomControl2)

myList.ForEach(Sub(i) Literal1.Text &= i.DoSomething.CssClass & "<br />")

The result is, the CssClass property of both someCustomControl & someCustomControl2 are set to the respective values.

This shows that the interface function DoSomething was successfully called and the instance changed.

like image 36
ajakblackgoat Avatar answered Sep 28 '22 22:09

ajakblackgoat