可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.