windsim

Hi,
I have a project based on .Net 1.1 and VS 2003,now I am trying to upgrade it
to .Net 2.0 and VS 2005.The project passes the 'Build Solution',but When I
start Debug, it suddenly comes StackOverflowException and Debug stops.

The main Exception point is the function below:

public System.ComponentModel.PropertyDescriptorCollection
GetStatefulPropertyDescriptors()
{
DataObject.StatefulPropertyAttribute a = new
StatefulPropertyAttribute();
return this.GetProperties(new Attribute[] { a }); //exception point
//class System.Attribute that is base class for customer attributes
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple=false,
Inherited=true)]
public class StatefulPropertyAttribute : Attribute{}

Under .Net 1.1 and VS 2003 there is no problem, no exception for the
function, but why does it come StackOverflowException under .Net 2.0

This is an implementation of the ICustomTypeDescriptor
interface, class name is GlobalizedObject.

Thia whole loop starts when some external code calls the GetHashCode() of my
object of type DTMProperties (inherits from ModuleProperties, inherits from
GlobalizedObject). The stack trace shows below shows that my
ModuleProperties.GetHashCode() method calls ModuleProperties.ToString().

The ToString method calls the GlobalizedObject.GetProperties(...) method in
order to return the name/value pairs of all properties that have a special
attribute.

The code that now executes does the following:

public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection globalizedProps;
PropertyDescriptorCollection baseProps =
TypeDescriptor.GetProperties(this, attributes, true);
...
}

The next thing happening according to the stack trace, is that we call into
some external code (probably the TypeDescriptor.GetProperties(...)), and then
we get back into the GetHashCode. Thus an infinite loop.

So the question is whether the .NET 2.0 implementation of
TypeDescriptor.GetProperties(...) calls the GetHashCode method for some
reason now And was that not the case in 1.1

Here is a more readable version of my stack trace:
.... (it just repeats itself from here)
[External Code]
SystemFrameworks.DataObject.GlobalizedObject.GetProperties(System.Attribute[] attributes = {Dimensions:[1]})
SystemFrameworks.DataObject.ModuleProperties.GetStatefulPropertyDescriptors()
SystemFrameworks.DataObject.ModuleProperties.GetModulePropertyListCurrent(SystemFrameworks.DataObject.ModuleProperties
mp = {SystemFrameworks.DataObject.DTMProperties})
SystemFrameworks.DataObject.ModuleProperties.ToString()
SystemFrameworks.DataObject.ModuleProperties.GetHashCode()
[External Code]

SystemFrameworks.DataObject.GlobalizedObject.GetProperties(System.Attribute[]
attributes = {Dimensions:[1]})
SystemFrameworks.DataObject.ModuleProperties.GetStatefulPropertyDescriptors()
SystemFrameworks.DataObject.ModuleProperties.GetModulePropertyListCurrent(SystemFrameworks.DataObject.ModuleProperties
mp = {SystemFrameworks.DataObject.DTMProperties})
SystemFrameworks.DataObject.ModuleProperties.ToString()
SystemFrameworks.DataObject.ModuleProperties.GetHashCode()
[External Code]

--
windsim



Re: Visual C# General StackOverflowException with TypeDescriptor.GetProperties(...)

TaylorMichaelL

The method can, and probably will, call GetHashCode as caching is used heavily internally. However this isn't the problem per se. You have a fundamental design flaw in your code. GHC can not change once an object is created. Therefore it must always return the same value. Using ToString to get the resulting hash code violates this rule.

Here's the justification. Imagine this class definition:

public class SomeClass

{

public int Id { get; set; }

public string Name { get; set; }

public override int GetHashCode ( )

{ return Name.GetHashCode(); };

}

Now instantiate an instance and insert it into a cache collection using GHC.

SomeClass cls = new SomeClass(1, "Hello");

cache.Add(cls);

Now you change the name of the object:

cls.Name = "Goodbye";

Now try to find it in the cache..you can't. The hash code changed. Even if you passed the original variable to the cache it won't find it because the hash code has changed. If the hash code of an object changes then dictionaries are useless.

In general you do not need to override GHC for reference types. The default implementation is fast, efficient and correct. For value types it is a lot harder and you generally do so but unfortunately without using immutable value types it is almost impossible to implement GHC correctly. The reason is because of another rule of .NET that I didn't mention: equality. Objects that have the same hash code must be equal. If this is not the case then you can wipe out the wrong object when inserting things into a dictionary. Again, reference types work correctly and don't need to be handled. Value types can't.

So, in summary, yes it is possible for the underlying reflection code to use caching and therefore call GHC. You must ensure that your custom implementation always returns the same value for the same instance irrelevant of any property values. The best solution (like equality) is to leave the default implementation as provided by .NET. If you are working with value types then you need to create your own hashing code but you can not rely on any mutable values in the type. You also need to make sure that equality uses the exact same logic so two objects with the same hash code are equal, and vice versa.

Michael Taylor - 5/14/07

http://p3net.mvps.org





Re: Visual C# General StackOverflowException with TypeDescriptor.GetProperties(...)

Mark Dawson

Hi,

I don't believe this is accurate:

"Objects that have the same hash code must be equal. If this is not the case then you can wipe out the wrong object when inserting things into a dictionary"

When you insert something into a dictionary first objects are compared by their hashcodes as a quick check to see if they are equal, if they hashcodes are equal the Equals is called on the object to check if they really are equal. So even if you created an object and override the GetHashCode method to always return 1, as long as you have correctly implemented Equals then there will not be a problem for correctness. That means conversely that if you do override Equals then you must also override GetHashCode otherwise then things like Dictionaries etc will not work correctly due to the check first with the hashcod value before actually calling Equals.

Mark.






Re: Visual C# General StackOverflowException with TypeDescriptor.GetProperties(...)

TaylorMichaelL

I based my answer on the fundamental rules that were defined when .NET was introduced and subsequently what guidelines remain in the framework. GHC and Equals must always agree. I can't think of a single case where this is not true.

I believe your interpretation of how it works is somewhat incorrect. When dealing with hash tables (of which dictionaries are almost always implemented) there are two layers. The higher layer (and the one that gives the best performance) uses a hashing algorithm (GHC in .NET) to calculate a, hopefully unique, key. This key is used to store the object. A perfect hashing algorithm does not exist for arbitrary data so you must handle the case of collisions. In the case of collisions there are a variety of ways to deal with it but the most common is to store a list of objects that have the same hash key. In this case equality can be used to determine if the objects map (since the hash codes are assumed to match).

However there are still two cases where problems can occur. The first is when the updated object is inserted into the wrong bin (because the hash code changed). In this case the object will be in the dictionary twice which is generally bad. The second case is where an object maps to a new bin and it is equal to an existing, but not necessarily same, object. For example if you have two classes that both have a name property and both use the name for equality (but not hashing) then an instance of one class can accidentally overwrite an instance of the second class if their hash codes should ever line up. It can happen even without changing the hash code so it is more of a superset of the first case where not only did the original instance remain in the dictionary but the new instance was added as well.

It is yet another reason why, for reference types, you should use the default implementations and for value types use immutable objects. IMHO. However since GHC is used in other places as well it is best to follow the guidelines set up by MS.

Michael Taylor - 5/14/07

http://p3net.mvps.org





Re: Visual C# General StackOverflowException with TypeDescriptor.GetProperties(...)

windsim

But the problem is:

The Hashcode I use is to check whether properties in the PropertyGrid change or not due to the unique of hashcode. I override

public override string ToString()

{

return this.ToString(this.GetModulePropertyListCurrent(this));

}

and

public override int GetHashCode()

{

return this.ToString().GetHashCode();

}

two functions above to make sure that I got my properties' hashcodes.

So are there any other ways to think about this problem

windsim





Re: Visual C# General StackOverflowException with TypeDescriptor.GetProperties(...)

TaylorMichaelL

You definitely shouldn't use ToString or GetHashCode to do this. They server useful purposes elsewhere.

Since all you are really trying to determine is whether the properties of an object have changed or not you should instead handle the PropertyValueChanged event of the grid. In your current solution you can't tell what properties changed without parsing the returned string so this would map to a simple boolean flag that gets set when the event is called. Currently you must be storing the hash code somewhere (to know when it has changed) so you'd replace the hash code with the change flag. If you wanted to get more advanced you could store the property's that changed (and perhaps their value) in a dictionary. You would then have a list of all the properties that changed so you can do more advanced things.

I'm not sure what you're logic is for using the list of changes but you'll probably also want to consider handling the SelectedObjectsChanged event to reset any flags you use when the targeted object is changed. Otherwise you'll need to distinguish between the different properties of different objects.

Michael Taylor - 5/15/07

http://p3net.mvps.org





Re: Visual C# General StackOverflowException with TypeDescriptor.GetProperties(...)

Mark Dawson

"GHC and Equals must always agree. I can't think of a single case where this is not true."

That depends on how well you write you GHC method if you override it :-) It is posible that you have two distinct objects but they still calculate a matching hashcode, in this case the hash codes will be equal but the overriden implementation of Equals will show they are different (maybe your id field is a long but GHC only returns an int in which case there are likely to be collisions with GHC even though the objects are not equal if you are basing equality on the value of the id field in the overriden Equals method. I am referring to the case that for two distinct objects the hash codes may be equals but Equals can be different, not not that on an objects implementation of GetHashCode and Equals changing during its lifetime.

"When dealing with hash tables (of which dictionaries are almost always implemented) there are two layers. The higher layer (and the one that gives the best performance) uses a hashing algorithm (GHC in .NET) to calculate a, hopefully unique, key. This key is used to store the object. A perfect hashing algorithm does not exist for arbitrary data so you must handle the case of collisions. In the case of collisions there are a variety of ways to deal with it but the most common is to store a list of objects that have the same hash key. In this case equality can be used to determine if the objects map (since the hash codes are assumed to match). "

Agrred - I should have been more precise in my previous post when I was talking about equals being called, which is the case only when a GHC collision occurs.