Search code examples
.netvb.netgenericstype-constraints

Can type constraints be exclusive rather than inclusive?


I am working on a strange issue related to VB.NET and multiple generic interfaces. The class I have implements a generic interface twice by specifying two different generic type arguments. In an effort to find a solution to that question (listed below), I had an idea but couldn't find any resources to see if this is possible.

VB.NET Implement Mutliple Contravariant interface types

The MSDN documentation here: http://msdn.microsoft.com/en-us/library/d5x73970.aspx explains type constraint usage, which is helpful but not complete (or at least the language doesn't support what I want).

Normally a constraint is declared as:

Public Interface ICopiesFrom(Of TObject As Class)
    Sub CopyFrom(ByVal data As TObject)
End Interface

But lets say I wanted to exclude a possible generic type argument instead of restrict to a subset.

Public Sub Interface ICopiesFrom(Of TObject Not As SpecificBadType)

Can this be done?

It would seem like: How to call a generic method with type constraints when the parameter doesn't have these constraints? is a duplicate, but my question is a bit different because I'd like compile-time support.

Edit:

Here is an example use case (were something like this possible):

'Ideally, the interface would have a definition like this
Public Interface ICopiesFrom(Of TModel As Not ISecureType)
    Sub CopyFrom(ByVal data As TModel)
End Interface

'Target type to exclude
Public Interface ISecureType

    Property AccountValue As Decimal
    Property AccountNumber As String

End Interface

Public Class AccountModel

    Public Overridable Property AccountCreated As DateTime
    Public Overridable Property ReferredBy As Guid

End Class

Public Class DetailedAccountModel
    Inherits AccountModel
    Implements ISecureType

    Public Property AccountNumber As String Implements ISecureType.AccountNumber
    Public Property AccountValue As Decimal Implements ISecureType.AccountValue

End Class

Public Class ProfileModel

    Public Property UserName As String
    Public Property EmailAddress As String
    Public Property PhoneNumber As String

End Class

Public Class User 'Composite representation for a view
    Implements ICopiesFrom(Of ProfileModel)
    Implements ICopiesFrom(Of AccountModel)

    Public Property UserName As String
    Public Property EmailAddress As String
    Public Property PhoneNumber As String
    Public Property AccountCreated As DateTime
    Public Property ReferredBy As Guid

    Public Overridable Overloads Sub CopyFrom(ByVal data As ProfileModel) Implements ICopiesFrom(Of ProfileModel).CopyFrom
        If data IsNot Nothing Then
            Me.UserName = data.UserName
            Me.EmailAddress = data.EmailAddress
            Me.PhoneNumber = data.PhoneNumber
        End If
    End Sub

    Public Overridable Overloads Sub CopyFrom(ByVal data As AccountModel) Implements ICopiesFrom(Of AccountModel).CopyFrom
        If data IsNot Nothing Then
            Me.AccountCreated = data.AccountCreated
            Me.ReferredBy = data.ReferredBy
        End If
    End Sub

    Public Function AccountAge() As Double
        Return (DateTime.Now - AccountCreated).TotalDays
    End Function

End Class

In the above scenario, I would never want someone to be able to pass a DetailedAccountModel into the User class so that it could never "accidentally" be displayed and ideally this would be caught at compile time.

Any of the following would be an acceptable answer:

  • A way to accomplish this exactly
  • Alternative approaches to achieve the same outcome (or similar)
  • A confirmation that I am crazy and this can't be done (with sources of course).

Solution

  • No, there isn't a way to do this. You can ask for it to be considered as a language feature, but I think the language creators feel that this would encourage people to write code that violates the Open/Closed Principle.

    For example, you might know right now that there's a specific class that should not be used as a generic type, but what's to stop someone else from coming up with another type that you don't know about now. The fact that you're trying to exclude a particular type is a code smell that probably indicates there's another better way to approach the problem you're trying to solve. Perhaps you're using inheritance where you should be using composition. Perhaps you could benefit by Segregating your Interfaces, or using better Separation of Concerns. Maybe by not including this feature the language is actually helping us to write more maintainable code.