1

I created a Class that inherits List(Of TKey, TValue) and adds a few functions. Its purpose is not to add items to it at runtime, but when it is initialized. I would actually like to remove the add/delete methods from the class (as it currently exposes them from the inherited class).

If you see the TestApp Class you will see, if I add a List(Of String, Integer) to the class it works just fine. However, in the provided code below, when I try to use Me.Add() in the class constructor; to add a it does not work. It actually tells me that a String cannot be converted to TKey and an Integer cannot be converted to a TValue. Which is untrue! It works fine in the test app.

Any ideas? Here is the code. Two classes, the Vars class and a Test app.

Note: Even using the Overrides Add method from within the class, as Me.Add(String, Integer) does not work (same error)

Imports System
Imports System.Collections.Generic
Imports System.Text


Public Class Vars(Of TKey, TValue)

Inherits List(Of KeyValuePair(Of TKey, TValue))

''' <summary>
'''     Initializes the class and fills with Key->Value Pairs
''' </summary>
''' <remarks>
'''     This does not work when adding directly from the class. When I overload
''' the Add() function, and call it with a KeyValuePair(Of String, String) in a
''' class instance it works, see below
''' </remarks>
Public Sub New()
    Dim kv = New KeyValuePair(Of TKey, TValue)("This", 1)
    Me.Add(kv)
End Sub

''' <summary>
'''     Adds an item to the class by String, Integer
''' </summary>
''' <param name="aKey">A String containing the Key of the element to be added.</param>
''' <param name="aValue">An Integer containing the Value of the element to be added.</param>
''' <remarks>
'''     This Works fine when called as in the demo shown below.
''' </remarks>
Public Overloads Sub Add(ByVal aKey As TKey, ByVal aValue As TValue)
    Dim kv = New KeyValuePair(Of TKey, TValue)(aKey, aValue)
    Me.Add(kv)
End Sub

''' <summary>
'''     Finds a Value stored in the class based on a given Key.
''' </summary>
''' <param name="key">A String containing a Key to search for.</param>
''' <remarks>Works.</remarks>
Public Function FindByKey(ByVal key As TKey) As List(Of KeyValuePair(Of TKey, TValue))
    Return Me.FindAll(Function(item As KeyValuePair(Of TKey, TValue)) (item.Key.Equals(key)))
End Function

''' <summary>
'''     Finds a Key stored in the class based on a given Value.
''' </summary>
''' <param name="value">An Integer containing the Value to search for.</param>
''' <remarks>Works</remarks>
Public Function FindByValue(ByVal value As TValue) As List(Of KeyValuePair(Of TKey, TValue))
    Return Me.FindAll(Function(item As KeyValuePair(Of TKey, TValue)) (item.Value.Equals(value)))
End Function

End Class

Public Class TestApp
    Shared Sub Main()
        Dim varTest As Vars(Of String, Integer) = New Vars(Of String, Integer)
        varTest.Add("Kung", 1)
        varTest.Add("Foo", 2)

        For Each var In varTest
            Console.WriteLine(var.Key & ":" & var.Value)
        Next

        'Output would be
        '
        '  Kung:1
        '  Foo:2
    End Sub
End Class
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
Mike L.
  • 1,936
  • 6
  • 21
  • 37

2 Answers2

7

A number of things going on here.

  1. Specifically: Is your class intended to be generic? That is, are you going to be using Vars(Of String, Integer), Vars(Of Object, DateTime), Vars(Of Double, Object) and all sorts? If so, then what would you want to happen on this line:

    Dim kv = New KeyValuePair(Of TKey, TValue)("This", 1)
    

    when TKey is not String, and TValue is not Integer ? The only thing you can add to a List(Of KeyValuePair(Of TKey, TValue)) is a KeyValuePair(Of TKey, TValue), not just any old KeyValuePair.

  2. More generally: List(Of T) is not designed to be inherited from (this comes up a lot, that's just the first example answer I found). By all means have your class contain a List(Of T), and keep items in it, but rather than inherit, you should decide exactly what you want the public interface of your class to be, and implement only that. It may be that implementing IList or some other pre-defined interface gives you the contract you want, but not necessarily.

  3. Even more generally: When you find yourself saying "I would actually like to remove the add/delete methods from the class", that's a massive hint that you shouldn't be inheriting from that class. The Liskov substitution principle says (roughly) that anything that inherits from a class C should be able to be treated like a C: if you inherit from List, callers will quite naturally expect to be able to Add and Delete. If you don't want those methods on your public interface, don't inherit from that class.

Community
  • 1
  • 1
AakashM
  • 62,551
  • 17
  • 151
  • 186
  • This is a great answer. The only thing I might add is that when talking about a new class it can quite easily just be a wrapper around List exposing just what is wanted. ie it contains a List and saves any work on recreating that but it just passes through most calls to that list. I just figure that this is almost certainly the desired solution so its worth maybe mentioning it. And if not its here in this comment anyway. :) – Chris Feb 17 '12 at 12:01
  • Great answer, just what I needed to hear. – Mike L. Feb 22 '12 at 18:49
0

To build on the other answer here to also include a suggestion of how to solve the problem, this is a classic case where composition should be used, rather than inheritance. Create a type that wraps up a list as a private member. The new type will have only the method and properties you want. It will be easy to implement, but somewhat tedious, as each method or property in the new type will merely call into the appropriate method in the original.

Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794