HashTable.Add Modifies HashTable Items?

C

CoderHead

I've got a Web app wherein I store a number of generic collections
(Generic.List) in the application-scope as a HashTable (a make-shift
cache), indexed by an Enum value - i.e.,
CType(Application.Item("SysTables"),
HashTable).Add(DataAttribute.Database.Recruiting, TableList). The Enum
values are as follows:

Enum DataAttribute.Database
Data = 0
DataAccess = 1
EventLog = 2
Recruiting = 3
End Enum

I load each of the collections using a method that I call with each of
the four Enum values. Each collection contains a List of a class
called SystemTable that, among other things, contains a property called
"Database" - a DataAttribute.Database Enum value, like so:

Public Property Database() As DataAttribute.Database
Get
Return InDatabase
End Get
Set(ByVal Value As DataAttribute.Database)
InDatabase = Value
End Set
End Property

As I load each collection into the application-scope HashTable, the
previously-added HashTable items are being modified. For instance,
let's say that the first collection I add is
CType(Application.Item("SysTables"),
HashTable).Add(DataAttribute.Database.Data, TableList), where each
SystemTable in TableList has a "Database" property value of "Data."
When I add the second item (CType(Application.Item("SysTables"),
HashTable).Add(DataAttribute.Database.DataAccess, TableList)), I find
that each one of my SystemTable instances in the first item (Data) now
have a "Database" property value of "DataAccess."

How in the world is the .Add method on HashTable modifying a single
property value of each of the members of an item contained in the
collection? Why isn't it changing the "ID" property or the "Name"
property? Since when does the .Add property modify existing members
anyway?
 
C

CoderHead

This test code works, and it's nearly identical to the code I'm running
in my Web app (aside from the obvious complexity of loading a class
from a database, etc.):

Module Module1
Private _dictionary As New Dictionary(Of Class1.DatabaseEnum, List(Of
Class1))

Sub Main()
_dictionary.Add(Class1.DatabaseEnum.Data,
GetCollection(Class1.DatabaseEnum.Data))
_dictionary.Add(Class1.DatabaseEnum.DataAccess,
GetCollection(Class1.DatabaseEnum.DataAccess))
_dictionary.Add(Class1.DatabaseEnum.EventHistory,
GetCollection(Class1.DatabaseEnum.EventHistory))
_dictionary.Add(Class1.DatabaseEnum.Recruiting,
GetCollection(Class1.DatabaseEnum.Recruiting))
End Sub

Private Function GetCollection(ByVal InDatabase As
Class1.DatabaseEnum) As List(Of Class1)
Dim ClassList As New List(Of Class1)
For count As Integer = 0 To 9
ClassList.Add(New Class1(InDatabase))
Next
Return ClassList
End Function
End Module
 
C

CoderHead

I don't know if anybody cares at this point or not, but here's the
solution to the Enum problem (note that it's an Enum problem and not a
HashTable or Dictionary problem).

Enums cannot be used as Shared members of a base class. There's no
documentation to support this, but here's what I found: when I use an
Enum as a Shared member of a base class, the last value populated in
the base class is the value returned from all subclasses. That means
whenever I instantiate a new subclass and I give it a new value for the
Enum member, all previously instantiated subclasses now return the new
value when accessed. First of all, WTF? Second, what is the thinking
behind this? My Shared String and Integer members don't change their
values when a new subclass is instantiated. An Enum is a structure and
not a type, but why would that mean that an instance of an Enum can
only hold a single value across the entire application? Weird.
 
B

Brian Gideon

CoderHead said:
I don't know if anybody cares at this point or not, but here's the
solution to the Enum problem (note that it's an Enum problem and not a
HashTable or Dictionary problem).

Enums cannot be used as Shared members of a base class. There's no
documentation to support this, but here's what I found: when I use an
Enum as a Shared member of a base class, the last value populated in
the base class is the value returned from all subclasses. That means
whenever I instantiate a new subclass and I give it a new value for the
Enum member, all previously instantiated subclasses now return the new
value when accessed. First of all, WTF? Second, what is the thinking
behind this? My Shared String and Integer members don't change their
values when a new subclass is instantiated. An Enum is a structure and
not a type, but why would that mean that an instance of an Enum can
only hold a single value across the entire application? Weird.

CoderHead,

That's the behavior I would expect from a Shared (static) class member.
If you want each instance of the class to have its own value then
don't modify the variable using the 'Shared' keyword. Also, can you
post a short, but complete program demonstrating that shared (static)
string and integer members behave differently than enums?

Brian
 
J

Jon Skeet [C# MVP]

CoderHead said:
I don't know if anybody cares at this point or not, but here's the
solution to the Enum problem (note that it's an Enum problem and not a
HashTable or Dictionary problem).

Enums cannot be used as Shared members of a base class. There's no
documentation to support this, but here's what I found: when I use an
Enum as a Shared member of a base class, the last value populated in
the base class is the value returned from all subclasses. That means
whenever I instantiate a new subclass and I give it a new value for the
Enum member, all previously instantiated subclasses now return the new
value when accessed. First of all, WTF? Second, what is the thinking
behind this? My Shared String and Integer members don't change their
values when a new subclass is instantiated. An Enum is a structure and
not a type, but why would that mean that an instance of an Enum can
only hold a single value across the entire application? Weird.

There's nothing weird about the enum, but I believe you're entirely
mistaken about your String and Integer members.

There's only *one* copy of any particular Shared variable for the
entire AppDomain. Different subclasses don't get extra copies. The
variable is associated with the type it's declared in.
 
C

CoderHead

I learned something today. A Shared member in a base class is shared
only to identical instances of the derived class. So if I have Class1
and Class2 that derive from BaseClass, instantiating Class2 won't
affect the Shared members of Class1. For example:
---------------------------------------------------------
Module Module1
Sub Main()
Dim MyClasses As New Collection()
MyClasses.Add(New Class1(AttributeClass.Test.One, "First Try", 100))
Console.WriteLine("Before MyClasses(1), MyClasses(0) = " &
MyClasses.Item(1).ToString())
MyClasses.Add(New Class2(AttributeClass.Test.Two, "Second Try", 200))
Console.WriteLine("MyClasses(0) = " & MyClasses.Item(1).ToString())
Console.WriteLine("MyClasses(1) = " & MyClasses.Item(2).ToString())
End Sub
End Module
---------------------------------------------------------
<AttributeUsage(AttributeTargets.Class)> Public Class AttributeClass
Inherits Attribute
Enum Test
One = 1
Two = 2
End Enum
Public EnumVar As Test = Test.One
Public StringVar As String = String.Empty
Public IntegerVar As Integer = 0
Public Sub New(ByVal EnumValue As Test, ByVal StringValue As String,
ByVal IntegerValue As Integer)
EnumVar = EnumValue
StringVar = StringValue
IntegerVar = IntegerValue
End Sub
End Class
---------------------------------------------------------
Public Class BaseClass(Of t)
Protected Shared EnumVar As AttributeClass.Test =
AttributeClass.Test.One
Public Shared StringVar As String = String.Empty
Public Shared IntegerVar As Integer = 0
Shared Sub New()
For Each Attr As Attribute In GetType(t).GetCustomAttributes(True)
If TypeOf Attr Is AttributeClass Then
EnumVar = CType(Attr, AttributeClass).EnumVar
StringVar = CType(Attr, AttributeClass).StringVar
IntegerVar = CType(Attr, AttributeClass).IntegerVar
End If
Next
End Sub
Public Sub New()
End Sub
Public Sub New(ByVal EnumValue As AttributeClass.Test, ByVal
StringValue As String, ByVal IntegerValue As Integer)
EnumVar = EnumValue
StringVar = StringValue
IntegerVar = IntegerValue
End Sub
Public Overrides Function ToString() As String
Return GetType(t).Name & ", EnumVar=" & EnumVar & ", StringVar='" &
StringVar & "', IntegerVar=" & IntegerVar.ToString()
End Function
End Class
---------------------------------------------------------
<AttributeClass(AttributeClass.Test.One, "Class1", 999)> Public Class
Class1
Inherits BaseClass(Of Class1)
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal EnumValue As AttributeClass.Test, ByVal
StringValue As String, ByVal IntegerValue As Integer)
MyBase.New(EnumValue, StringValue, IntegerValue)
End Sub
End Class
---------------------------------------------------------
<AttributeClass(AttributeClass.Test.Two, "Class2", 888)> Public Class
Class2
Inherits BaseClass(Of Class2)
Public Sub New()
MyBase.New()
End Sub
Public Sub New(ByVal EnumValue As AttributeClass.Test, ByVal
StringValue As String, ByVal IntegerValue As Integer)
MyBase.New(EnumValue, StringValue, IntegerValue)
End Sub
End Class

That explains why my other classes and other properties weren't being
affected. Because the only property I happened to be changing was the
Enum value.
 
C

CoderHead

I am admittedly mistaken about the String and Integer values, but I
believe you're mistaken about derived classes. Run the sample code I
attached a little while ago and you'll see that although Class1 and
Class2 are in the same AppDomain, they receive different copies of the
shared member variables. The proof is that I instantiate Class2 with
different values than Class1 but they both write their disparate values
to the Console. Don't believe me? Check it out. That's why this thing
has been confusing for me.
 
J

Jon Skeet [C# MVP]

CoderHead said:
I am admittedly mistaken about the String and Integer values, but I
believe you're mistaken about derived classes. Run the sample code I
attached a little while ago and you'll see that although Class1 and
Class2 are in the same AppDomain, they receive different copies of the
shared member variables.

Well, they have different base types effectively - one is
BaseClass(Of Class1) and the other is BaseClass(Of Class2).

I had indeed neglected to take account of generics, but that's why
you're seeing the different values - it has nothing to do with
subclasses really. Basically, BaseClass(Of Class1) has one set of
static (shared) variables, and BaseClass(Of Class2) has another set.
They're different types. Here's an example (in C#) demonstrating that:

using System;

class Counter
{
static int next=0;

public static int Next
{
get
{
return next++;
}
}
}

class Foo<T>
{
public static int x = Counter.Next;
}

class Test
{
static void Main()
{
Console.WriteLine (Foo<int>.x);
Console.WriteLine (Foo<string>.x);
Console.WriteLine (Foo<long>.x);
Console.WriteLine (Foo<char>.x);
}
}

Now, if you had a different subclass which still had the same base
class as one of the others, eg:

Public Class Class3 Inherits BaseClass(Of Class1)

then you'd see the shared variables being shared, because they both
genuinely have the same base class.
 
C

CoderHead

Jon said:
Well, they have different base types effectively - one is
BaseClass(Of Class1) and the other is BaseClass(Of Class2).

I had indeed neglected to take account of generics, but that's why
you're seeing the different values - it has nothing to do with
subclasses really. Basically, BaseClass(Of Class1) has one set of
static (shared) variables, and BaseClass(Of Class2) has another set.
They're different types. Here's an example (in C#) demonstrating that:

using System;

class Counter
{
static int next=0;

public static int Next
{
get
{
return next++;
}
}
}

class Foo<T>
{
public static int x = Counter.Next;
}

class Test
{
static void Main()
{
Console.WriteLine (Foo<int>.x);
Console.WriteLine (Foo<string>.x);
Console.WriteLine (Foo<long>.x);
Console.WriteLine (Foo<char>.x);
}
}

Now, if you had a different subclass which still had the same base
class as one of the others, eg:

Public Class Class3 Inherits BaseClass(Of Class1)

then you'd see the shared variables being shared, because they both
genuinely have the same base class.

Thanks for clearing that up, that actually helps me.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top