1

To avoid getting into the weeds on my particular program, let me just create a simplified case.

I have a generic class that should work on a variety of objects. Each of those objects must implement a certain interface.

What I WANT to say is something like:

Public Interface GenThing
    Shared Function thing_name() As String ' This doesn't work! Can't be shared!
    Sub FillOne(row As DataRow)
End Interface

public class Thing1
  implements GenThing
    public shared function thing_name() as string implements GenThing.thing_name
        return "thing number one"
    end function

    public sub FillOne(row as DataRow) implements GenThing.MakeOne
        ... bunch of work ...
    end sub
end class

public class ThingUtil(of T as {GenThing,New})
    public function GetList(id as integer) as List(of T)
      dim name=T.thing_name() ' This doesn't work!
      dim ds as DataSet=GetData(name,id) ' bunch of work here that's the whole point of the class but not relevant to the question
      dim my_list = new List(of T)
      for each row as DataRow in ds.tables(0).rows
          dim my_t = new T()
          my_t.FillOne(row)
          my_list.add(my_t)
      next
      return my_list
    end function
end class

Do you get my problem? I need every class that implements the interface to have a function that returns a "name" that is used to get the data that is needed to create an instance of the object. But I need to know this name BEFORE I create the instance, because I need it to be able to create the instance. But VB doesn't allow an interface to have a shared function, so what I want to write doesn't work.

So what I've done is this:

I make thing_name not shared.

Then instead of simply "dim name=T.thing_name()", I write

dim dummy = new T()
dim name = dummy.thing_name()

Okay, it works, but it seems really ugly. I create an instance of the object, with all the overhead that that involves, just to get a piece of constant text.

Is there a better way? Or am I making a big deal out of nothing?

Update

I see that two people voted to close this question on the grounds that it is the same as "Why can't we have shared functions in an interface?"

I am not asking why I can't have a shared. I am saying, GIVEN that I can't, how do I solve this particular problem?

Jay
  • 26,876
  • 10
  • 61
  • 112
  • Why not simply mark the method as `Shared` in the implementing class? In other words, have the method declaration in the interface, but then decorate it with `Shared` in the class(e) that implement the interface? – Tim May 29 '15 at 15:37
  • possible duplicate of [Why we can not have Shared(static) function/methods in an interface/abstract class?](http://stackoverflow.com/questions/330318/why-we-can-not-have-sharedstatic-function-methods-in-an-interface-abstract-cla) – GSerg May 29 '15 at 15:40
  • If thing_name is shared, then calling from a instance should give you a warning. You have to call shared method from the class name ex: Thing1.thing_name() that's why it can't be in an interface... You could have non-shared method that calls the shared method. – the_lotus May 29 '15 at 15:57
  • @tim Because the generic class only knows about the interface, not the specific implementation. If it's not shared in the interface, then the generic class can't call it as a shared function. Besides which, the program doesn't compile if the signature in the implementing class doesn't match the interface. – Jay May 29 '15 at 15:59
  • Can you use that string as class name? Then you can get it with `GetType(YourType).Name` – Fabio May 29 '15 at 20:23
  • I'm trying to add this logic to existing classes. I suppose I COULD rename all the classes, though co-workers might beat me up. I was thinking that I could get the class name, then look it up in a table or a case statement or whatever to translate to the desired string. The obvious catch is that then any time I added the interface to a class, I'd have to also had the name to the look-up function. Certainly do-able. – Jay May 29 '15 at 22:26

2 Answers2

3

There's no really simple way of fixing this, no.

Depending on what thing_name does, however, you might approach things in a different way. If each implementation just returns a constant value, then it's effectively metadata about the class - and could be described in an attribute instead, which can be fetched at execution time. (See Type.GetCustomAttributes.) Unfortunately you can't then enforce all types implementing the interface to be decorated with the attribute - but you could write a unit test to check this pretty easily.

If thing_name needs to really do work at execution time, that's tougher. You could potentially look for a well-known shared method name instead and execute that via reflection (and again have unit tests to check that it's implemented properly).

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • It is a simple constant string. In each implementing class, the function is a simple one line 'return "whatever"'. The custom attributes idea is interesting, though as you say it couldn't be enforced at compile time, Visual Studio wouldn't automatically add the function when you type the implements statement, etc. Doing it by reflection is certainly possible but seems more painful than what I'm doing. :-) – Jay May 29 '15 at 16:04
  • 1
    If it's just a constant string, then this sounds like precisely what attributes are for - it's metadata about the class, really. It would be great if you *could* treat attributes as part of an interface, but I'm afraid that's not the way things are. I'd go for the attribute path and add a unit test. You could always write your own code diagnostic in Roslyn if you do this a lot. – Jon Skeet May 29 '15 at 16:06
  • Debatable if it's logically metadata about the class. It's a text string used to look up data in a database, more like an attribute of the class than metadata. But whatever, one could debate that sort of technicality endlessly -- and unproductively. I think I'm mainly taking the first sentence of your answer as the answer here: There is no clean solution that meets all the requirements one might fantasize seeing: easy to implement, enforced at compile time, purpose obvious to future readers, etc. – Jay Jun 11 '15 at 15:14
1

I realize this is from a few years ago, but running into a similar problem, I wanted to offer a different solution. Pass a delegate as parameter to the ThingUtil constructor. You avoid having to put a shared method in an interface, and the constructor will force you to include the parameter at compile time.

You can add more delegates if needed, or to make it even simpler in this case, just pass name as a string instead of get_name as a delegate.

Define the delegate in the interface:

Public Interface GenThing
  Delegate Function ThingNameDelegate() As String
  Sub FillOne(row As DataRow)
End Interface

Public Class Thing1
  Implements GenThing

  Public Shared Function thing_name() As String 'name this whatever you want
    Return "thing number one"
  End Function

  Public Sub FillOne(row As DataRow) Implements GenThing.FillOne
      'do stuff
  End Sub
End Class

In ThingUtil, add a member to store the delegate, a constructor parameter to to accept, and call it with .Invoke():

Public Class ThingUtil(Of T As {GenThing, New})
        Private m_thing_name As GenThing.ThingNameDelegate

        Public Sub New(thing_name As GenThing.ThingNameDelegate)
            m_thing_name = thing_name
        End Sub
        Public Function GetList(id As Integer) As List(Of T)
            Dim name = m_thing_name.Invoke()
            Dim ds As DataSet = GetData(name, id) ' bunch of work here that's the whole point of the class but not relevant to the question
            Dim my_list = New List(Of T)
            For Each row As DataRow In ds.Tables(0).Rows
                Dim my_t = New T()
                my_t.FillOne(row)
                my_list.Add(my_t)
            Next
            Return my_list
        End Function
End Class

Finally, use it like this:

Dim tu as new ThingUtil(Of Thing1)(AddressOf Thing1.get_name)
tu.GetList(1)