BUG: Unhandled exception error occurs when you enumerate through the Hashtable (815634)



The information in this article applies to:

  • Microsoft .NET Framework 1.1
  • Microsoft .NET Framework 1.0
  • Microsoft Visual Basic .NET (2003)
  • Microsoft Visual Basic .NET (2002)
  • Microsoft Visual C# .NET (2003)
  • Microsoft Visual C# .NET (2002)

SYMPTOMS

In a Remoting scenario, you may try to move the Enumerator for a Hashtable from the server side to the client side. You do this so you can iterate through the Hashtable on the client side. While you are enumerating through the Hashtable, you may receive the following error message:

An unhandled exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
Additional information: Collection was modified; enumeration operation may not execute.

CAUSE

The error occurs because the Hashtable Enumerator is a MarshalByValue component. Therefore, the Hashtable stores the owner of the Hashtable internally. When you request the Hashtable on the client side, the Hashtable is deserialized on the client side. During deserialization, the internal Hashtable is reconstructed. Therefore, the enumerator fails.

WORKAROUND

To work around this problem, return the Hashtable directly to the client. Do this instead of returning IEnumerator for the Hashtable. For example, locate the following code in the ISystemSetting.cs file or in the ISystemSetting.vb file of the InterfaceDll project. Sample code that can be used is included in the "More Information" section of this article.

Visual C# .NET Code
IEnumerator GetEnumerator();
Visual Basic .NET Code
Function GetEnumerator() As IEnumerator
Replace the IEnumerator function with the HashTable function as follows:

Visual C# .NET Code
Hashtable GetHashtable();
Visual Basic .NET Code
Function GetHashTable() As Hashtable

STATUS

Microsoft has confirmed that this is a bug in the Microsoft products that are listed at the beginning of this article.

MORE INFORMATION

Steps to Reproduce the Problem

  1. Run Visual Studio .NET. Create a new Class Library project.

    You can use either Visual Basic .NET or Visual C# .NET. Name the project InterfaceDLL.
  2. Replace the existing code in the class with the following code:

    Visual Basic .NET Code
    Option Strict On
    Imports System
    Imports System.Collections
    Imports System.Runtime.Serialization
    Imports System.Collections.Specialized
    
    Namespace test.SystemSettingInterface
    
       Public Interface ISystemSetting
          ' Add elements to the Hashtable
          Sub Add(ByVal Key As Object, ByVal Value As Object)
    
          ' Function Returning IEnumerator for Hashtable
          ' This function throws error
          Function GetEnumerator() As IEnumerator
    
          ' Function Returning Hashtable directly
          ' This function can be used as a workaround for function GetEnumerator
          Function GetHashTable() As Hashtable
    
          ' Default Property for Enumeration
          Default Property Item(ByVal key As Object) As Object
          ' Count Returns the number of elements in the Hashtable
          ReadOnly Property Count() As Integer
          ' Test function
          Sub Test()
    
       End Interface
    End Namespace
    

    Visual C# .NET Code
    using System;
    using System.Collections;
    using System.Runtime.Serialization;
    using System.Collections.Specialized;
    
    namespace test.SystemSettingInterface
    {
    	public interface ISystemSetting
    	{
          // Add elements to the Hashtable
    		void Add( object Key, object Value );
    
          // Function Returning IEnumerator for Hashtable
          // This function throws error
          IEnumerator GetEnumerator();
          
          // Function Returning Hashtable directly
          // This function can be used as workaround for function GetEnumerator
    	   Hashtable GetHashtable();
    
          // Default Property for Enumeration
    		object this[object key]
    		{
             get;
    			set;
    		}
    
          // Count Returns the number of elements in the Hashtable
    		int Count
    		{
    			get;
    		}
    
          // Test function
          void Test();
    	}  
    }
    
  3. On the Build menu, click Build Solution to create InterfaceDLL.dll.
  4. On the File menu, point to Add Project and then click New Project.
  5. Add a new Console Application project to the solution.

    You can use either Visual Basic .NET or Visual C# .NET. Name the project Server.
  6. In Solution Explorer, right-click Server and then click Add Reference.
  7. In the Reference dialog box, click the Projects tab. Double-click InterfaceDLL and then click OK.
  8. Replace the existing code with the following code:

    Visual Basic .NET Code
    Option Strict On
    Imports System
    Imports System.Runtime.Remoting
    Imports System.Runtime.Remoting.Channels
    Imports System.Runtime.Remoting.Channels.Tcp
    Imports System.Collections
    Imports System.Collections.Specialized
    Imports InterfaceDLL.test
    Imports InterfaceDLL.test.SystemSettingInterface
    
    Namespace test.server
       Public Class SystemSettingTest : Inherits MarshalByRefObject
          Implements ISystemSetting
    
          ' HashTable used for Enumeration
          Public Shared m_HashTable As Hashtable
          Private Shared m_initialized As Boolean
    
          Shared Sub Main()
             Dim m_systemSettingTest As SystemSettingTest = New SystemSettingTest()
             m_systemSettingTest.Init()
             Console.WriteLine("Test Server Running.")
             Console.ReadLine()
          End Sub
    
          Public Sub Test() Implements ISystemSetting.Test
             If IsNothing(m_HashTable) Then
                ' Create new instance the HashTable
                m_HashTable = New Hashtable()
    
                ' Add elements to the Hashtable
                m_HashTable.Add("testkey1", "testvalue1")
                m_HashTable.Add("testkey2", "testvalue2")
             End If
          End Sub
    
          Private Sub Init()
             Try
                ' Register Channel for Remoting
                ChannelServices.RegisterChannel(New TcpChannel(9999))
                RemotingConfiguration.RegisterWellKnownServiceType(GetType(SystemSettingTest), "SystemSettingTest", WellKnownObjectMode.SingleCall)
                m_initialized = True
    
             Catch ex As Exception
                m_initialized = False
                Throw New System.Exception("Unable to establish WKS: " + ex.Message)
             End Try
          End Sub
    
          ' Add method implementation 
          Public Sub Add(ByVal Key As Object, ByVal Value As Object) Implements ISystemSetting.Add
             m_HashTable.Add(Key, Value)
          End Sub
    
          ' Count method implementation
          Public ReadOnly Property Count() As Integer Implements ISystemSetting.Count
             Get
                Return m_HashTable.Count
             End Get
          End Property
    
          ' GetEnumerator method implementation. This method generates error
          Public Function GetEnumerator() As System.Collections.IEnumerator Implements ISystemSetting.GetEnumerator
             ' Return the Enumerator for the Hashtable
             Dim T As IDictionaryEnumerator = m_HashTable.GetEnumerator()
             Return T
          End Function
    
          ' This is the Workaround method that directly returns the Hashtable instead of Enumerator
          Public Function GetHashTable() As System.Collections.Hashtable Implements InterfaceDLL.test.SystemSettingInterface.ISystemSetting.GetHashTable
             Return m_HashTable
          End Function
    
          ' Item property method implementation
          Default Public Property Item(ByVal key As Object) As Object Implements ISystemSetting.Item
             Get
                Return m_HashTable(key)
             End Get
             Set(ByVal Value As Object)
             End Set
          End Property
    
       End Class
    
    End Namespace
    
    Visual C# .NET Code
    using System;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Tcp;
    using System.Collections;
    using System.Collections.Specialized;
    using test.SystemSettingInterface;
    using test;
    
    namespace test.server
    {
    	 
    	public class SystemSettingTest : MarshalByRefObject, ISystemSetting
    	{
    		static void Main(string[] args)
    		{
    			SystemSettingTest m_systemSettingTest = new SystemSettingTest();
    			m_systemSettingTest.Init();
    			Console.WriteLine ("Test Server Running.");
    			Console.ReadLine();
    		}
    
          // HashTable used for Enumeration
    		public static Hashtable m_HashTable;
    		
    		private static bool m_initialized;
    
    		public void Test()
    		{
    			if(m_HashTable==null)
    			{
    				// Create new instance the HashTable
    				m_HashTable= new Hashtable();
                
                // Add elements to the Hashtable
    				m_HashTable.Add("testkey1", "testvalue1");
    				m_HashTable.Add("testkey2", "testvalue2");
    			}
    		}
    		private void Init()
    		{
    			try
    			{
    			   // Register Channel for Remoting
                ChannelServices.RegisterChannel(new  TcpChannel(9999));
    				RemotingConfiguration.RegisterWellKnownServiceType(typeof(SystemSettingTest),
    					"SystemSettingTest", WellKnownObjectMode.SingleCall);
    					
    				m_initialized=true;
    			}
    			catch (System.Exception ex)
    			{
    				m_initialized=false;
    				throw new System.Exception("Unable to establish WKS: " + ex.Message);
    			}
    		}
          // Add method implementation
    		public void Add(object Key, object Value)
    		{
    			m_HashTable.Add(Key,Value);
    		}
    		
          // GetEnumerator method implementation. This method generates error
    		public IEnumerator GetEnumerator()
    		{
             // Return the Enumerator for the Hashtable
    			IDictionaryEnumerator T = m_HashTable.GetEnumerator();
             return T;
    		}
    
          // This is the Workaround method that directly returns the Hashtable instead of Enumerator
          public Hashtable GetHashtable()
          {
             return m_HashTable;
          }
    		
          // Default property for Enumeration
          public object this [object key]
    		{
    			get
    			{
    				return m_HashTable[(string)key];
    			}
    			set
    			{
    			}
    		}
    
          // Count property implementation
    		public int Count
    		{
    			get
    			{
    				return m_HashTable.Count;
    			}
    		}
    	}
    }
    
  9. In Solution Explorer, right-click Server and then click Set as StartUp Project.
  10. On the Debug menu, click Start.
  11. Open a new instance of Visual Studio .NET development environment.
  12. Create a new Windows application project.

    You can use either Visual Basic .NET or Visual C# .NET. Name the project Client.
  13. In Solution Explorer, right-click Client and then click Add Reference.
  14. Click Browse and then select InterfaceDLL.dll that you created in step 3.
  15. Click OK to add the reference.
  16. Replace the existing code with the following code:

    Visual Basic .NET Code
    Option Strict On
    
    Imports System.Net
    Imports System.Runtime.Remoting
    Imports System.Runtime.Remoting.Channels
    Imports System.Runtime.Remoting.Channels.Tcp
    Imports InterfaceDLL.test
    Imports InterfaceDLL.test.SystemSettingInterface
    
    Public Class Form1
       Inherits System.Windows.Forms.Form
    
    #Region " Windows Form Designer generated code "
    
       Public Sub New()
          MyBase.New()
    
          'This call is required by the Windows Form Designer.
          InitializeComponent()
    
          'Add any initialization after the InitializeComponent() call
    
       End Sub
    
       'Form overrides dispose to clean up the component list.
       Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
          If disposing Then
             If Not (components Is Nothing) Then
                components.Dispose()
             End If
          End If
          MyBase.Dispose(disposing)
       End Sub
    
       'Required by the Windows Form Designer
       Private components As System.ComponentModel.IContainer
    
       'NOTE: The following procedure is required by the Windows Form Designer
       'It can be modified using the Windows Form Designer.  
       'Do not modify it using the code editor.
       Friend WithEvents button3 As System.Windows.Forms.Button
       Friend WithEvents button2 As System.Windows.Forms.Button
       Friend WithEvents button1 As System.Windows.Forms.Button
       Friend WithEvents Button4 As System.Windows.Forms.Button
       <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
          Me.button3 = New System.Windows.Forms.Button()
          Me.button2 = New System.Windows.Forms.Button()
          Me.button1 = New System.Windows.Forms.Button()
          Me.Button4 = New System.Windows.Forms.Button()
          Me.SuspendLayout()
          '
          'button3
          '
          Me.button3.Location = New System.Drawing.Point(32, 106)
          Me.button3.Name = "button3"
          Me.button3.Size = New System.Drawing.Size(208, 32)
          Me.button3.TabIndex = 5
          Me.button3.Text = "GetEnumerator (Error)"
          '
          'button2
          '
          Me.button2.Location = New System.Drawing.Point(32, 61)
          Me.button2.Name = "button2"
          Me.button2.Size = New System.Drawing.Size(208, 32)
          Me.button2.TabIndex = 4
          Me.button2.Text = "Count Hashtable elements"
          '
          'button1
          '
          Me.button1.Location = New System.Drawing.Point(32, 16)
          Me.button1.Name = "button1"
          Me.button1.Size = New System.Drawing.Size(208, 32)
          Me.button1.TabIndex = 3
          Me.button1.Text = "Initialize Remote Component"
          '
          'Button4
          '
          Me.Button4.Location = New System.Drawing.Point(32, 151)
          Me.Button4.Name = "Button4"
          Me.Button4.Size = New System.Drawing.Size(208, 32)
          Me.Button4.TabIndex = 6
          Me.Button4.Text = "GetHashtable (Work around)"
          '
          'Form1
          '
          Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
          Me.ClientSize = New System.Drawing.Size(280, 206)
          Me.Controls.AddRange(New System.Windows.Forms.Control() {Me.Button4, Me.button3, Me.button2, Me.button1})
          Me.Name = "Form1"
          Me.Text = "Form1"
          Me.ResumeLayout(False)
    
       End Sub
    
    #End Region
       Private m_SystemSetting As ISystemSetting
    
       ' Connect to server using Remoting
       Private Sub init()
          Dim connect As String = "tcp://" + Dns.GetHostName() + ":9999/SystemSettingTest"
          m_SystemSetting = CType(RemotingServices.Connect(GetType(ISystemSetting), connect), ISystemSetting)
          m_SystemSetting.Test()
       End Sub
    
       ' Initialize the Hashtable
       Private Sub button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles button1.Click
          Try
             init()
             MessageBox.Show("Init success.")
          Catch ex As Exception
    
             MessageBox.Show("Unable to initialize remoting connection with test server:" + ex.Message)
          End Try
       End Sub
    
       ' Verify the Hashtable has elements
       Private Sub button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles button2.Click
          Try
             MessageBox.Show(m_SystemSetting.Count.ToString())
          Catch ex As Exception
             MessageBox.Show(ex.Message)
          End Try
       End Sub
    
       ' Problem method call
       Private Sub button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles button3.Click
          ' Get the Enumerator for Hashtable
          Dim T As IDictionaryEnumerator = CType(m_SystemSetting.GetEnumerator(), IDictionaryEnumerator)
          MessageBox.Show(T.Value.ToString())
       End Sub
    
       ' Work Around method call
       Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
          Dim ht As Hashtable
          ht = m_SystemSetting.GetHashTable()
          Dim obj As Object
          For Each obj In ht.Values
             MessageBox.Show(obj.ToString())
          Next
       End Sub
    End Class
    
    Visual C# .NET Code
    using System;
    using System.Net;
    using System.Drawing;
    using System.Collections;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Runtime.Remoting;
    using System.Runtime.Remoting.Channels;
    using System.Runtime.Remoting.Channels.Tcp;
    using test.SystemSettingInterface;
    
    namespace test.client
    {
       public class Form1 : System.Windows.Forms.Form
       {
          private ISystemSetting m_SystemSetting;
          private System.Windows.Forms.Button button1;
          private System.Windows.Forms.Button button2;
          private System.Windows.Forms.Button button3;
          private System.Windows.Forms.Button button4;
          private System.ComponentModel.Container components = null;
    
          public Form1()
          {
             InitializeComponent();
    
          }
    
          protected override void Dispose( bool disposing )
          {
             if( disposing )
             {
                if (components != null) 
                {
                   components.Dispose();
                }
             }
             base.Dispose( disposing );
          }
    
    		#region Windows Form Designer generated code
          /// <summary>
          /// Required method for Designer support - do not modify
          /// the contents of this method with the code editor.
          /// </summary>
          private void InitializeComponent()
          {
             this.button1 = new System.Windows.Forms.Button();
             this.button2 = new System.Windows.Forms.Button();
             this.button3 = new System.Windows.Forms.Button();
             this.button4 = new System.Windows.Forms.Button();
             this.SuspendLayout();
             // 
             // button1
             // 
             this.button1.Location = new System.Drawing.Point(32, 16);
             this.button1.Name = "button1";
             this.button1.Size = new System.Drawing.Size(208, 32);
             this.button1.TabIndex = 0;
             this.button1.Text = "Initialize Remote Component";
             this.button1.Click += new System.EventHandler(this.button1_Click);
             // 
             // button2
             // 
             this.button2.Location = new System.Drawing.Point(32, 58);
             this.button2.Name = "button2";
             this.button2.Size = new System.Drawing.Size(208, 32);
             this.button2.TabIndex = 1;
             this.button2.Text = "Count Hashtable elements";
             this.button2.Click += new System.EventHandler(this.button2_Click);
             // 
             // button3
             // 
             this.button3.Location = new System.Drawing.Point(32, 100);
             this.button3.Name = "button3";
             this.button3.Size = new System.Drawing.Size(208, 32);
             this.button3.TabIndex = 2;
             this.button3.Text = "GetEnumerator (Error)";
             this.button3.Click += new System.EventHandler(this.button3_Click);
             // 
             // button4
             // 
             this.button4.Location = new System.Drawing.Point(32, 142);
             this.button4.Name = "button4";
             this.button4.Size = new System.Drawing.Size(208, 32);
             this.button4.TabIndex = 3;
             this.button4.Text = "GetHashtable (work around)";
             this.button4.Click += new System.EventHandler(this.button4_Click);
             // 
             // Form1
             // 
             this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
             this.ClientSize = new System.Drawing.Size(280, 198);
             this.Controls.AddRange(new System.Windows.Forms.Control[] {
                                                                          this.button4,
                                                                          this.button3,
                                                                          this.button2,
                                                                          this.button1});
             this.Name = "Form1";
             this.Text = "Form1";
             this.ResumeLayout(false);
    
          }
    		#endregion
    
          [STAThread]
          static void Main() 
          {
             Application.Run(new Form1());
          }
    
          // Initialize the Hashtable
          private void button1_Click(object sender, System.EventArgs e)
          {
    
             try
             {
                init();
                MessageBox.Show("Init success.");
             }
             catch(System.Exception ex)
             {
                MessageBox.Show("Unable to initialize remoting connection with test server:" + ex.Message);
             }
    			
          }
    
          // Connect to server using Remoting
          private void init()
          {
             string connect="tcp://" + Dns.GetHostName() + ":9999/SystemSettingTest";
             m_SystemSetting=(ISystemSetting)RemotingServices.Connect(typeof(ISystemSetting), connect);
             m_SystemSetting.Test();
    		
          }
    
          // Verify the Hashtable has elements
          private void button2_Click(object sender, System.EventArgs e)
          {
             try
             {
                MessageBox.Show(this.m_SystemSetting.Count.ToString());
             }
             catch(System.Exception ex)
             {
                MessageBox.Show(ex.Message);
             }
    			
          }
    
          // Problem method call
          private void button3_Click(object sender, System.EventArgs e)
          {
             IDictionaryEnumerator T = (IDictionaryEnumerator)m_SystemSetting.GetEnumerator();
             // Get the Enumerator for Hashtable
             MessageBox.Show(T.Value.ToString());
          }
    
          // Work Around method call
          private void button4_Click(object sender, System.EventArgs e)
          {
             Hashtable ht;
             ht=m_SystemSetting.GetHashtable();
             foreach(Object obj in ht.Values)
             {
                MessageBox.Show(obj.ToString());
             }
          }
       }
    }
    
  17. On the Debug menu, click Start.
  18. Click Initialize Remote Component. Click OK to close the Init success dialog box.
  19. Click Count Hashtable elements.

    You see 2 appear.
  20. Click GetEnumerator (Error).

    You receive the error in the "Symptoms" section.
  21. If you click GetHashtable (work around) after step 18, you can successfully iterate through the Hashtable.

REFERENCES

For additional information, click the following article numbers to view the articles in the Microsoft Knowledge Base:

307933 HOW TO: Work with the HashTable Collection in Visual Basic .NET

309357 HOW TO: Work with the HashTable Collection in Visual C# .NET


Modification Type:MinorLast Reviewed:9/13/2005
Keywords:kbvs2002sp1sweep kbRemoting kbCollections kbClient kbBCL kbConfig kbbug KB815634 kbAudDeveloper