Valhalla Legends Forums Archive | .NET Platform | Default property of a class

AuthorMessageTime
Grok
I'm not figuring out how to properly write a default property to a class in the way I want.  According to the documentation, a default property of a class must be a property procedure that takes at least one argument.  But the class I am creating does not need to take a argument.

[code]
Public Class ObjectID
    Dim mObjectID As String
    Default Public Property TheValue(ByVal p1 As String) As String
        Get
            TheValue = mObjectID        'not using p1 ... why is it there?
        End Get
        Set(ByVal Value As String)
            mObjectID = Value             'again p1 not used
        End Set
    End Property
End Class

Dim obj1 As ObjectID = New ObjectID

'desired usage:
obj1 = "ABC.DEF.456"
MsgBox(obj1)

'forced usage:
obj1("") = "ABC.DEF.456"
MsgBox(obj1(""))
[/code]

How can I get around this requirement that a default property be one that must take at least one argument?

What would be nice is if that required argument could be Optional, then I could define a default value for it as in this unallowed way:
[code]
    Default Public Property TheValue(Optional ByVal p1 As String = New String("")) As String
[/code]
December 6, 2005, 3:15 PM
kamakazie
You can't because what happens if you were to do something like:

[code]
Dim obj1 As New ObjectId()
Dim obj2 As New ObjectId()

obj1 = obj2
[/code]

Should it set the default properties of obj1 to obj2? Or set obj1's reference to obj2?
December 6, 2005, 5:45 PM
Myndfyr
Well there are a couple things that you should know.

The .NET Framework no longer supports default properties as a language feature; this is done to enforce type safety.  This feature of Visual Basic .NET is the equivalent to providing a custom indexer operator overload.  In C#, you would do the following:
[code]
public string this[string index] {
//...
}
[/code]

To provide default initialization values, you can use the New subroutine (the class constructor):
[code]
    Public Sub New()
        mObjectId = String.Empty
    End Sub

    Public Sub New(ByVal id As String)
        mObjectId = id
    End Sub
[/code]

Also, if you were using C# to construct this particular class, you could overload the implicit-cast operator.  This may or may not work for Visual Basic .NET; it appears not to.
[code]
public class ObjectID
{
private string mObjectId;
public static implicit operator ObjectID(string idVal)
{
ObjectID id = new ObjectID();
id.mObjectId = idVal;
return id;
}

public static implicit operator string(ObjectID idVal)
{
return idVal.mObjectId;
}

public static void TestUsage()
{
ObjectID obj = "Test";
string id = obj;
Console.WriteLine(id);
}
}
[/code]
That compiles with no warnings.

It appears that implicit operator overloads do not work in VB:
[code]
Imports CSImplicit.ObjectID

Public Class Class1
    Public Shared Sub Test()
        Dim objId As CSImplicit.ObjectID
        objId = "Test"
        Dim id As String
        id = objId
        Console.WriteLine(id)
    End Sub
End Class
[/code]
[pre]Preparing resources...
Updating references...
Performing main compilation...
C:\Dev Projects\DefProbProj\Class1.vb(6) : error BC30311: Value of type 'String' cannot be converted to 'CSImplicit.ObjectID'.
C:\Dev Projects\DefProbProj\Class1.vb(8) : error BC30311: Value of type 'CSImplicit.ObjectID' cannot be converted to 'String'.
Building satellite assemblies...
Satellite assemblies could not be built because the main project output is missing.[/pre]
So, I guess you're out of luck.  You *could* just provide a property, you know.  Or, don't try to use .NET classes as typedefs.
December 6, 2005, 5:48 PM
Grok
Why does a String class work in such a way that I cannot duplicate with my own classes?
I want to create an ObjectID class that works just like String, complete with default property, and conversion functions:

[code]
Dim myValue As String

myValue = New String("Florida")          'default property gets used here!
MsgBox(myValue)                'default property gets used here!

MsgBox(myValue.ToLower)    'returns lower-case version of default property value
[/code]
December 6, 2005, 8:01 PM
Myndfyr
Strings are implicit data types in the .NET languages that come with Visual Studio (with the possible exception of J#).  For example, in C#, the expression:
[code]
string lowerVal = "My Uppercased Text".ToLower();
[/code]
is valid.  Note the application of the class method on the string literal; the compiler interprets the literal as an instance of the System.String class.

Also, the Visual Basic assignment:
[code]
Dim myValue As String = "Florida"
[/code]
is also valid for the same reason.  The creation of a System.String object is actually taken care of by the compiler (via the ldstr IL instruction).

In your example, there are no default properties being used.  myValue is a reference to a string, and were you to try to determine its value before assignment, it would return null or Nothing in VB.  Assigning a new string object is actually making the "myValue" variable itself point to the System.String instance in memory.  In .NET, like in Java, object references (unless they are struct types derived from System.ValueType) function roughly equivalent to pointers.

In your example of MsgBox(myValue), you are incorrect in assessing that the default property is used.  The parameter of the MsgBox function (which is actually a wrapper for System.Windows.Forms.MessageBox.Show()) is a string object instance.

Finally, about the ToLower() method -- this is a method, not a property, and it returns a new class instance of String that contains a copy of the original string converted to lowercase.  .NET performs string interning so that strings in memory are duplicated as rarely as possible.

I compiled your code in a short sub.  This is the resultant IL (via ildasm):
[code]
.method public static void  Main(string[] args) cil managed
{
  .entrypoint
  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
  // Code size      42 (0x2a)
  .maxstack  3
  .locals init ([0] string myValue)
  IL_0000:  nop
  IL_0001:  ldstr      "Florida"
  IL_0006:  call      char[] [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.CharArrayType::FromString(string)
  IL_000b:  newobj    instance void [mscorlib]System.String::.ctor(char[])
  IL_0010:  stloc.0
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.0
  IL_0013:  ldnull
  IL_0014:  call      valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxResult [Microsoft.VisualBasic]Microsoft.VisualBasic.Interaction::MsgBox(object,
                                                                                                                                                            valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxStyle,
                                                                                                                                                            object)
  IL_0019:  pop
  IL_001a:  ldloc.0
  IL_001b:  callvirt  instance string [mscorlib]System.String::ToLower()
  IL_0020:  ldc.i4.0
  IL_0021:  ldnull
  IL_0022:  call      valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxResult [Microsoft.VisualBasic]Microsoft.VisualBasic.Interaction::MsgBox(object,
                                                                                                                                                            valuetype [Microsoft.VisualBasic]Microsoft.VisualBasic.MsgBoxStyle,
                                                                                                                                                            object)
  IL_0027:  pop
  IL_0028:  nop
  IL_0029:  ret
} // end of method Form1::Main
[/code]

This code is silly, but I want you to recognize the information on line IL_001b:
[code]
callvirt  instance string [mscorlib]System.String::ToLower()
[/code]
callvirt tells the runtime to call ToLower() as a virtual function, instance tells the runtime that the function is going to use the top stack object as the this pointer for an instance, string tells the runtime that the return value will be a string, [mscorlib] tells the runtime what assembly the function is in, System.String tells the assembly what type owns the function, and finally ::ToLower() tells the runtime what function to execute.  The instruction before it:
[code]  IL_001a:  ldloc.0[/code]
loads the local object 0 onto the stack.  And, as metadata for the function provides, there is only one local object, local object 0:
[code]
  .locals init ([0] string myValue)
[/code]

I hope this enlightens you a bit to how the .NET framework works with strings.

December 6, 2005, 11:19 PM

Search