Default properties in VB.NET?

2020-01-30 05:00发布

问题:

In the early days of .NET, I believe there was an attribute you could decorate a class with to specify a default property.

According to some articles I've found, this appears to have been yanked from the framework at some point, because it was a little confusing, and I can see how that is the case.

Still, is there another way to get the functionality it provided?

It looked something like this:

<DefaultProperty("Value")> _  
Public Class GenericStat
    ...
    Public Property Value() As Integer
        ...
    End Property
    ...
End Class

This allowed you to do Response.Write(MyObject) instead of Response.Write(MyObject.Value)... This is not a terribly clunky example, but in some complex object-oriented contexts it gets a little hideous. Please let me know if there is a better way.

Note: I am not looking for the Default keyword, which can only be used on properties that take a parameter.

回答1:

Well, the .NET framework does have a notion of a default member. Key ingredients are the DefaultMemberAttribute class and Type.GetDefaultMembers(). In VB.NET, specifying the default member is part of the language syntax:

  Public Class Sample
    Private mValue As Integer
    Default Public ReadOnly Property Test(ByVal index As Integer) As Integer
      Get
        Return index
      End Get
    End Property
  End Class

Use it like this:

  Sub Main()
    Dim s As New Sample
    Console.WriteLine(s(42))
    Console.ReadLine()
  End Sub

The compiler implements this by emitting [DefaultMember] automatically. This however has a restriction, the property must have an index argument, specifically to avoid the syntax ambiguity. This restriction is not enforced when specifying the attribute explicitly:

  <System.Reflection.DefaultMember("AnotherTest")> _
  Public Class Sample
    Public ReadOnly Property AnotherTest() As Integer
      Get
        Return 42
      End Get
    End Property
  End Class

But that default member would only be accessible as a default by a language that allows such syntax. For which I don't know an example in .NET, this was used back in the COM days, like VB6. Also the core reason behind VB6 having the Set keyword, it solves the ambiguity and states "I mean the object, not the object's default property". Very painful syntax detail for many beginning Visual Basic programmers back then.

C# has the exact same rules, but doesn't allow the same kind of flexibility. You've probably seen the indexer before:

  public class Sample {
    public int this[int index] {
      get { return index; }
    }
  }

This code also makes the compiler output the [DefaultMember] attribute. The named property in that attribute is "Item". And that's why you see the indexer documented and indexed in the MSDN Library as "Item".



回答2:

I've found that you can do exactly what the original poster wanted using Widening Operator CType This was mentioned above but without much detail, so I entirely missed it as I was trying to find an answer to this question. This methodology doesn't define a default property, per se, but it achieves the same result.

Public Class GenericStat
    ...
    Public Property Value() As Integer
    ...
    End Property
    ...
    'this could be overloaded if needed
    Public Sub New(ByVal Value As Integer)
        _Value = Value
    End Sub
    '
    Public Shared Widening Operator CType(ByVal val As Integer) As GenericStat
        Return New GenericStat(val)
    End Operator
    '
    Public Shared Widening Operator CType(ByVal val As GenericStat) As Integer
        Return val.Value
    End Operator
End Class

So now

Dim MyObject as GenericStat
MyObject = 123

and

Dim Int as Integer
Int = MyObject   

both work without the .Value reference and without an indexer such as myobject(1)



回答3:

In this example it pulls the object, but does not convert it to an integer.

Brian, I don't see why your desired effect cannot be achieved using a Widening Operator CType. The code you showed us can be made to work. However, beware of implicit conversions. They're generally not a good idea.



回答4:

No, that was explicitly removed from VB 7.

When you have a long chain of default properties, knowing exactly what will be returned is hard. When both b and c have a Foo method, does a.Foo(1) mean a.b.Foo(1) or a.b.c.Foo(1)?

The real kicker was Set. By dropping default properties, they were also able to drop the Set keyword for object assignment.



回答5:

I've been looking for an answer to a similar problem and in the process I stumbled across this here. Actually John's answer pointed me into the direction I needed to go. And it might help with the original question as well:

My Problem: I needed something that I could use just like an Integer

Dim myVal as Integer
myVal = 15
If myVal = 15 then
  ...
End If

...and so on... However I needed additional things as well

myVal.SomeReadOnlyProperty (as String)
myVal.SomeOtherReadOnlyProperty (as Integer)

(actually those readonly Properties could be Methods as well ...)
etc... So I really needed an Object

I was thinking of extension methods for Integer ( @ _ @ ) ... I didn't want to go down that road ...

I also thought of writing a "ReadOnlyPropertyOracle" as a separate class and give it Methods like

GetSomeReadOnlyProperty(ByVal pVal as Integer) as String
GetSomeOtherReadOnlyProperty(ByVal pVal as Integer) as Integer

weeeell .... That would have worked but looked gruesome ...

So in came John's Hack and Brian MacKay's comment about operators: Combining both, widening/narrowing conversion operators (for assignment) and comparison operators for ... well comparision. Here is part of my code and it does what I need:

'The first two give me the assignment operator like John suggested
Public Shared Widening Operator CType(ByVal val As Integer) As MySpecialIntType
    Return New MySpecialIntType(val)
End Operator

'As opposed to John's suggestion I think this should be Narrowing?
Public Shared Narrowing Operator CType(ByVal val As MySpecialIntType) As Integer
    Return val.Value
End Operator

'These two give me the comparison operator
'other operators can be added as needed
Public Shared Operator =(ByVal pSpecialTypeParameter As MySpecialIntType, ByVal pInt As Integer) As Boolean
    Return pSpecialTypeParameter.Value = pInt
End Operator

Public Shared Operator <>(ByVal pSpecialTypeParameter As MySpecialIntType, ByVal pInt As Integer) As Boolean
    Return pSpecialTypeParameter.Value <> pInt
End Operator

Yes, this will still be 1-2 dozen one-line operator definitions but most of them are trivial with little room for error ;-) So this works for me...



回答6:

To answer my own question, operator overloading seemed to be an interesting solution here.

In the end, it wasn't a good fit.

I ended up having to add in about 17 1-line methods, which meant lots of room for bugs. More important is that you can't overload the assignment operator; the overload for = is for equality testing only.

So even with this, I can't say:

Dim x as Integer = MyObject.Stats(Stat.Health) ...In this example it pulls the object, but does not convert it to an integer, so of course the result is an exception.

What I really need is a good old fashioned default property, but I think those days are over.



回答7:

There is a DefaultProperty attribute so your example should be correct, but this seems to be for components which are used in the Windows Forms desinger.



回答8:

You could override the ToString method to output Value as a string so that when you do Response.Write(MyObject), you get the same effect.

    Public Overrides Function ToString() As String
        Return Me.Value.ToString 
    End Function

[EDIT] Now that I understand it better, why not just provide a way to get directly at the values of the contained objects.

Public Class MyClass
    Private m_Stats(100) As Stats  ' or some other collection'

    Public Property StatValue(ByVal stat_number As Integer) As _
        Integer
        Get
            Return m_Stats(stat_number)
        End Get
        Set(ByVal Value As Integer)
            m_Stats(stat_number) = Value
        End Set
    End Property
End Class


回答9:

Hi John your answer was very useful! I changed for use with any type, thanks.

Public Class GenericStat(Of Ttype)

Public Property Value As Ttype
'
Public Sub New()

End Sub
'
'this could be overloaded if needed
Public Sub New(ByVal Value As Ttype)
   _Value = Value
End Sub
'
Public Shared Widening Operator CType(ByVal val As Ttype) As GenericStat(Of Ttype)
   Return New GenericStat(Of Ttype)(val)
End Operator
'
Public Shared Widening Operator CType(ByVal val As GenericStat(Of Ttype)) As Ttype
   Return val.Value
End Operator

End Class

And the usage:

Dim MyInteger As GenericStat(Of Integer)
MyInteger = 123

Dim Int As Integer
Int = MyInteger

Dim MyString As GenericStat(Of String)
MyString = "MyValue"

Dim Str As String
Str = MyString


回答10:

You can still use the attribute if you import System.ComponentModel.

As others mentioned, this is not ideal since VB.Net prefers that you use the Default attribute. Of course, that comes with conditions, which doesn't really help (requiring an index, for example).

But if you use

Imports System.ComponentModel

it allows you to use the attribute on your class.



标签: .net vb.net oop