2

I'm going to show VB.NET code first because the behavior of its C# equivalent is more confusing (see below).

Consider the following three classes:

Public Class BaseClass
    Private Shared Rand As New Random
    Public Shared Function CreateDerived() As BaseClass
        Return If(Rand.Next(1, 3) = 1, New DerivedClass1(), New DerivedClass2())
    End Function
End Class

Public Class DerivedClass1
    Inherits BaseClass

    Sub New()
        MyProperty = 1
    End Sub

    Friend Property MyProperty As Integer
End Class

Public Class DerivedClass2
    Inherits BaseClass

    Sub New()
        MyProperty = 2
    End Sub

    Friend Property MyProperty As Integer
End Class

Now, when I try to do something like this:

Sub Foo()
    Dim targetClass As BaseClass = BaseClass.CreateDerived()

    Dim Casted
    If TypeOf (targetClass) Is DerivedClass1 Then
        Casted = DirectCast(targetClass, DerivedClass1)
    ElseIf TypeOf (targetClass) Is DerivedClass2 Then
        Casted = DirectCast(targetClass, DerivedClass2)
    Else
        Exit Sub
    End If

    Console.WriteLine(Casted.MyProperty) 'Throws an exception.
End Sub

I don't seem to be able to access MyProperty, and I receive the following exception:

Public member 'MyProperty' on type 'DerivedClass1' not found.

So, when I change the access level of MyProperty to Public, the code works as expected.

The weird part is when I try the C# equivalent of the above code on VS 2015, it works just fine, But on .NET Fiddler, it doesn't.

Here's the C# example on .NET Fiddler where I get the same behavior as VB.NET.

So, is there something I'm doing wrong?

  • Your casting in that `Foo` method is pointless because you're not assigning the result to a variable of that type anyway. You may see the behaviour you want if you were to do this: `Console.WriteLine(DirectCast(targetClass, DerivedClass1).MyProperty)` – jmcilhinney Nov 10 '17 at 10:00
  • Your C# and VB code is not equivalent because the C# uses `dynamic` which VB doesn't have. Your `Casted` variable is type `Object` in VB. – jmcilhinney Nov 10 '17 at 10:03
  • Presumably, late-binding in VB only works for `Public` members. I guess that makes sense because late-binding actually involves the member being accessed by code within the Framework and that means outside the assembly that that member is declared in, thus `Friend` members would be inaccessible. C#'s `dynamic` is different and presumably doesn't involve the same external access. – jmcilhinney Nov 10 '17 at 10:05
  • @jmcilhinney I know I can access the property when using `DirectCast` directly or when creating a variable of the type, but that's not my question. I'm wondering why I can access it *from a dynamic object* when it's public, but not when it's friend/internal. About C# `dynamic` keyword, AFAIK, it's the same as using object in VB.NET with `Option Strict Off`. – 41686d6564 stands w. Palestine Nov 10 '17 at 11:02
  • *"Presumably, late-binding in VB only works for Public members"* perhaps that's the reason, although late-binding can be useful sometimes while in the same assembly. Anyways, again, the C# example doesn't have the same behavior with all compilers. – 41686d6564 stands w. Palestine Nov 10 '17 at 11:05
  • There's no such thing as "a dynamic object" in VB. Your variable is simply type `Object`. That's it, that's all. You can't access the `Friend` property because VB late-binding is implemented such that the specified member will be access by code outside the assembly it's declared in and the whole point of a `Friend` member is that that's not possible. `dynamic` is part of C#'s implementation of late-binding but that doesn't mean that it's the same implementation as is used by VB. – jmcilhinney Nov 10 '17 at 11:07
  • Hmm, point taken, thank you. Actually I suspected that, and that's why I decided to test it with C# but it made me more confused when the C# example had the same behavior on .NET Fiddle. – 41686d6564 stands w. Palestine Nov 10 '17 at 11:13

1 Answers1

0

I can't answer the WHY of this question, but I can provide a workaround.

Create an Interface and implement it in your BaseClass. Then instead of declaring Casted as Object, you can declare it as the interface, which will properly expose the property you want, with the same access modifier as before.

Full example below.

Sub Foo()
    Dim targetClass As BaseClass = BaseClass.CreateDerived()

    Dim Casted As Interf
    If TypeOf (targetClass) Is DerivedClass1 Then
        Casted = DirectCast(targetClass, DerivedClass1)
    ElseIf TypeOf (targetClass) Is DerivedClass2 Then
        Casted = DirectCast(targetClass, DerivedClass2)
    Else
        Exit Sub
    End If

    Console.WriteLine(Casted.MyProperty) 'Throws an exception.
End Sub

Friend Interface Interf
    Property MyProperty As Integer
End Interface

Public Class BaseClass
    Implements Interf

    Private Shared Rand As New Random

    Friend Overridable Property MyProperty As Integer Implements Interf.MyProperty

    Public Shared Function CreateDerived() As BaseClass
        Return If(Rand.Next(1, 3) = 1, New DerivedClass1(), New DerivedClass2())
    End Function
End Class

Public Class DerivedClass1
    Inherits BaseClass

    Sub New()
        MyProperty = 1
    End Sub

    Friend Overrides Property MyProperty As Integer
End Class

Public Class DerivedClass2
    Inherits BaseClass

    Sub New()
        MyProperty = 2
    End Sub

    Friend Overrides Property MyProperty As Integer
End Class
A Friend
  • 2,750
  • 2
  • 14
  • 23