vb.netclassvariablesinheritanceshadows

VB.NET Inheritance issue


I've got A base class Base and Sorter and Parser classes derived from it . The same thing with BaseResult with derived SorterResult and ParserResult. Base has a Result field of BaseResult type, BaseResult has a Log field. The reason why I've used a Base class, is because both of Parser and Sorter must write a Log. Here's my code:

Public MustInherit Class Base
    Public Result As BaseResult
    Event LogChanged()
    Protected Sub AddLogLine(ByVal _logString As String)
        If Not String.IsNullOrWhiteSpace(_logString) Then Result.Log.Add(_logString)
        RaiseEvent LogChanged()
    End Sub
End Class

Public Class Sorter
    Inherits Base
    Public Shadows Result As SorterResult
    Sub New()
        Result = New SorterResult With {.Log = New List(Of String)}
    End Sub
    Sub Go()
        AddLogLine("Sorter started")
    End Sub
End Class

Public Class Parser
    Inherits Base
    Public Shadows Result As ParserResult

    Sub New()
        Result = New ParserResult With {.Log = New List(Of String)}
    End Sub
    Sub Go()
        AddLogLine("Sorter started")
    End Sub
End Class

Public MustInherit Class BaseResult
    Public Log As List(Of String)
End Class

Public Class SorterResult
    Inherits BaseResult
    '//SorterResult fields
End Class

Public Class ParserResult
    Inherits BaseResult
    '//ParsedResult fields
End Class

The issue here is that compiler sais(on pic below):enter image description here

"variable 'Result' conflicts with variable 'Result' in the base class 'Base' and should be declared 'Shadows'." When I used Shadows keyword, warning disappeared, but I get a null reference exception on this line, because Result field is Nothing:

If Not String.IsNullOrWhiteSpace(_logString) Then Result.Log.Add(_logString)

I can't assign a value to a Result variable in Base class constructor, because It must be of type SorterResult in Sorter, and ParserResult in Parser. What is the regular pattern here? Sorry my bad english.

enter image description here


Solution

  • Use generics

    Public MustInherit Class Base(Of TResult As BaseResult)
        Public Result As TResult
    
        Event LogChanged()
    
        Protected Sub AddLogLine(ByVal _logString As String)
            If Not String.IsNullOrEmpty(_logString) Then Result.Log.Add(_logString)
            RaiseEvent LogChanged()
        End Sub
    
        Public MustOverride Sub Go()
    End Class
    
    Public Class Sorter
        Inherits Base(Of SorterResult)
    
        Sub New()
            Result = New SorterResult With {.Log = New List(Of String)}
        End Sub
    
        Public Overrides Sub Go()
            AddLogLine("Sorter started")
        End Sub
    End Class
    
    Public Class Parser
        Inherits Base(Of ParserResult)
    
        Sub New()
            Result = New ParserResult With {.Log = New List(Of String)}
        End Sub
    
        Public Overrides Sub Go()
            AddLogLine("Sorter started")
        End Sub
    End Class
    

    However, this is not a "beautiful" inheritance hierarchy. Inheritance should formulate relations like "a student is a person" where student derives from person. What do sorters and parsers have in common? Are they a Base? Are they loggers? Are they commands (as suggests the Go method)? Is inheritance required here? Wouldn’t it be more appropriate to use aggregation? I would declare a completely independent logger class and inject it into classes. This allows you to be more flexible, as it enables you to inject different types of loggers.

    Public MustInherit Class Logger
        Public Event LogChanged()
    
        Public MustOverride Sub AddLogLine(ByVal message As String)
    
        Protected Sub OnLogChanged()
            RaiseEvent LogChanged()
        End Sub
    End Class
    
    Public Class TextFileLogger
        Inherits Logger
    
        Public Overrides Sub AddLogLine(ByVal message As String)
            If Not String.IsNullOrEmpty(message) Then
                'TODO: Write message to log file
                OnLogChanged()
            End If
        End Sub
    End Class
    

    You can inject it like this:

    Public Class SomeConsumerClass
        Private _logger As Logger
    
        Sub New(ByVal logger As Logger)
            _logger = logger
        End Sub
    
        Public Sub DoSomething()
            _logger.AddLogLine("Did something!")
        End Sub
    End Class
    

    Use like this:

    Dim obj As New SomeConsumerClass(New TextFileLogger())
    obj.DoSomething()
    

    If you have another kind of logger (XmlFileLogger, StringListLogger, DatabaseLogger...) it is now easy to use it without having to change all the classes using it.

    Maybe you should even have only one global logger:

    Dim globalLogger As New TextFileLogger()
    Dim sorter As New Sorter(globalLogger)
    Dim parser As New Parser(globalLogger)