|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionUsing Generic types together with What is the problem with generic types having statics? The problem is that generic types do not share the same Using the CodeI have attached to this article a set of C# code files that may be included in the Visual Studio project of your choice with the version of IDE that you currently use. The only requirement is that your code runs on .NET Framework 2.0 since this is the version that introduced Generics into C#. I have also used NUnit 2.4. Problem DescriptionWhen you code a generic class and include a using System;
using System.Collections.Generic;
namespace Dotway.Singleton.GenericStatic
{
public class GenericBadSingletonCollection where T : class, new()
{
private static Dictionary<Type, object> instances =
new Dictionary<Type, object>();
public static T GetInstance()
{
object instance;
if (!instances.TryGetValue(typeof(T), out instance))
{
instance = new T();
instances.Add(typeof(T), instance);
}
return (T)instance;
}
public static int CountInstances { get { return instances.Count; } }
}
}
Admittedly, the code sample here described has a bit of a giveaway in the naming of the class: There is something wrong here - something bad. Can you spot it? The code seems straight forward enough; it's the To make testing simple, I am using two trivial domain classes with no content at all throughout my code: namespace Dotway.Singleton.GenericStatic
{
public class Square {}
public class Triangle {}
}
All I am interested in is to instantiate them as To reveal the error, I have two Unit Tests which both pass even though the second one is designed to show the error! I opted for creating passing tests only since this is a tutorial and not the code you should copy and use in your project. So again to be clear; all tests are green but some reveal the error(s)! [Test]
public void GenericSingletonWithCollection_HasOneInstancePerGenericType()
{
Square square = GenericBadSingletonCollection<Square>.GetInstance();
Square square2 = GenericBadSingletonCollection<Square>.GetInstance();
Assert.IsNotNull(square);
Assert.IsNotNull(square2);
Assert.AreSame(square, square2);
Assert.AreEqual(1, GenericBadSingletonCollection<Square>.CountInstances);
}
[Test]
public void EachGenericSingletonInstances_HasItsOwnMemorySpace()
{
GenericBadSingletonCollection<Square>.GetInstance();
GenericBadSingletonCollection<Triangle>.GetInstance();
Assert.AreEqual(1, GenericBadSingletonCollection<Square>.CountInstances);
Assert.AreEqual(1, GenericBadSingletonCollection<Triangle>.CountInstances);
}
The first test is fine and shows that if I call The second test however reveals the error! I have declared a This is the whole root of the problem: Each public class GenericSimpleSingletonCollection<T> where T : class, new()
{
private static T instance;
public static T GetInstance()
{
if (instance == default(T))
{
instance = new T();
}
return instance;
}
public static int CountInstances { get { return instance == null ? 0 : 1; } }
}
We can use a single variable to hold the instance and always return And here are the same tests: [Test]
public void GenericSimpleSingleton_HasOneInstancePerGenericType()
{
Square square = GenericSimpleSingletonCollection<Square>.GetInstance();
Square square2 = GenericSimpleSingletonCollection<Square>.GetInstance();
Assert.IsNotNull(square);
Assert.IsNotNull(square2);
Assert.AreSame(square, square2);
Assert.AreEqual(1, GenericSimpleSingletonCollection<Square>.CountInstances);
}
[Test]
public void EachGenericSimpleSingletonInstances_HasItsOwnMemorySpace()
{
GenericSimpleSingletonCollection<Square>.GetInstance();
GenericSimpleSingletonCollection<Triangle>.GetInstance();
Assert.AreEqual(1, GenericSimpleSingletonCollection<Square>.CountInstances);
Assert.AreEqual(1, GenericSimpleSingletonCollection<Triangle>.CountInstances);
}
However the problem with this simplified solution remains the same. There is a bug in the code that our current tests are not catching. We cannot accurately count the number of Before we do that, however, let’s return to the ”good ol´ days” of non-Generics and compare code: public class OldSchoolSingletonCollection
{
private static Dictionary<Type, object> instances = new Dictionary<Type, object>();
public static object GetInstance(Type type)
{
object instance;
if (!instances.TryGetValue(type, out instance))
{
instance = Activator.CreateInstance(type, null);
instances.Add(type, instance);
}
return instance;
}
public static int CountInstances { get { return instances.Count; } }
}
The ” [Test]
public void OldSchoolSingletonCollection_HasOneInstancePerGenericType()
{
Square square = (Square)OldSchoolSingletonCollection.GetInstance(typeof(Square));
Square square2 = (Square)OldSchoolSingletonCollection.GetInstance(typeof(Square));
Assert.IsNotNull(square);
Assert.IsNotNull(square2);
Assert.AreSame(square, square2);
Assert.AreEqual(1, OldSchoolSingletonCollection.CountInstances);
}
[Test]
public void OldSchoolSingletonCollection_HasItsOwnMemorySpace()
{
OldSchoolSingletonCollection.GetInstance(typeof(Square));
OldSchoolSingletonCollection.GetInstance(typeof(Triangle));
Assert.AreEqual(2, OldSchoolSingletonCollection.CountInstances);
}
But as you can see, this code runs exactly as required! The non-generic and generic versions of the same code do not behave alike. Herein lays the problem I am writing about in this article. While the Let us, at this time, turn to the documentation on Generics and see what we find: I've scanned the documentation located at MSDN > MSDN Library > Development Tools and Languages > Visual Studio 2008 > Visual Studio > Visual C# > C# Programming Guide > Generics (C# Programming Guide) for documentation on this behavior. I personally cannot find a section in any of the many subpages that explain this. All I can find is a passage at the bottom of the page ”Generics in the Run Time” that speaks about how a generic type is managed by the Run Time: “The first time a generic type is constructed with any reference type, the runtime creates a specialized generic type with object references substituted for the parameters in the MSIL. Then, every time that a constructed type is instantiated with a reference type as its parameter, regardless of what type it is, the runtime reuses the previously created specialized version of the generic type. This is possible because all references are the same size. For example, suppose you had two reference types, a class Customer { } class Order { } Stack<Customer> customers;
At this point, the runtime generates a specialized version of the Stack<Order> orders = new Stack<Order>();
Unlike with value types, another specialized version of the customers = new Stack<Customer>();
As with the previous use of the Moreover, when a generic C# class is instantiated by using a value type or reference type parameter, reflection can query it at runtime and both its actual type and its type parameter can be ascertained.” This does not explain Open and Closed types. Perhaps it is somewhere else in the documentation? Please link in the comments if you like so we can all read it! Signature TaylorMichaelL explains this very well in the comments below so I thought I'd edit it in (Thanks Taylor!): “When the CLR runs across a closed type instantiation (or reference) it first checks to see if the closed type has been created. If it hasn't then it creates the final closed type, allocates memory for the So there you go! This is why it does not work as expected. Again I realize that this might be very much as you'd expect if you are used to these concepts! But if you are not, then this tutorial might help you see the difference! Step by Step Refinement of the SituationTime to begin solving the problem; or rather suggest ways to work around it! Let’s start with making the methods generic but not the class. Naturally this works fine so just for completeness, I'll show you that code. This is the same as the non-generic situation, only we don't have to cast in our own code. The cast is done inside the generic methods. Something I like to call ‘fake generics’! Real generics do not cast at all. public class GenericSimpleSingletonCollection<T> where T : class, new()
{
private static T instance;
public static T GetInstance()
{
if (instance == default(T))
{
instance = new T();
}
return instance;
}
public static int CountInstances { get { return instance == null ? 0 : 1; } }
}
And the tests: [Test]
public void SingletonContainerWithGenericMethods_HasOneInstancePerGenericType()
{
Square square = GenericSimpleSingletonCollection<Square>.GetInstance();
Square square2 = GenericSimpleSingletonCollection<Square>.GetInstance();
Assert.IsNotNull(square);
Assert.IsNotNull(square2);
Assert.AreSame(square, square2);
Assert.AreEqual(1, GenericSimpleSingletonCollection<Square>.CountInstances);
}
[Test]
public void SingletonContainerWithGenericMethods_SharesMemorySpace_AsAllways()
{
SingletonContainerWithGenericMethods.GetInstance<Square>();
SingletonContainerWithGenericMethods.GetInstance<Triangle>();
Assert.AreEqual(2, SingletonContainerWithGenericMethods.CountInstances);
}
But what about a generic class; that was the real problem we were solving. We need to move the state of the class so that it may be shared but we don't really care for anyone else to be able to access it. Intuitively one tries with a nested class: public class GenericSingletonContainerWithNestedStateClass<T> where T : class, new()
{
public static T GetInstance()
{
return StateClass.GetInstance();
}
public static int CountInstances { get { return StateClass.CountInstances; } }
private class StateClass
{
private static Dictionary<Type, object> instances =
new Dictionary<Type, object>();
public static T GetInstance()
{
object instance;
if (!instances.TryGetValue(typeof(T), out instance))
{
instance = new T();
instances.Add(typeof(T), instance);
}
return (T)instance;
}
public static int CountInstances { get { return instances.Count; } }
}
}
Does this work? Let’s see the tests: [Test]
public void GenericSingletonContainerWithNestedStateClass_HasOneInstancePerGenericType()
{
Square square = GenericSingletonContainerWithNestedStateClass<Square>.GetInstance();
Square square2 = GenericSingletonContainerWithNestedStateClass<Square>.GetInstance();
Assert.IsNotNull(square);
Assert.IsNotNull(square2);
Assert.AreSame(square, square2);
Assert.AreEqual(1, GenericBadSingletonCollection<Square>.CountInstances);
}
[Test]
public void GenericSingletonContainerWithNestedStateClass_HasItsOwnMemorySpace()
{
GenericSingletonContainerWithNestedStateClass<Square>.GetInstance();
GenericSingletonContainerWithNestedStateClass<Triangle>.GetInstance();
Assert.AreEqual(1,
GenericSingletonContainerWithNestedStateClass<Square>.CountInstances);
Assert.AreEqual(1,
GenericSingletonContainerWithNestedStateClass<Triangle>.CountInstances);
}
No! It does not work. The behavior is the same! We have to move the state of our container outside the generic class to be able to share it between generic+type instances: public class GenericSingletonContainerWithInternalStateClass<T> where T : class, new()
{
public static T GetInstance()
{
return InteralStateClass.GetInstance<T>();
}
public static int CountInstances { get { return InteralStateClass.CountInstances; } }
public static implicit operator T(
GenericSingletonContainerWithInternalStateClass<T> value)
{
return InteralStateClass.GetInstance<T>();
}
}
internal class InteralStateClass
{
private static Dictionary<Type, object> instances = new Dictionary<Type, object>();
public static T GetInstance<T>() where T : class, new()
{
object instance;
if (!instances.TryGetValue(typeof(T), out instance))
{
instance = new T();
instances.Add(typeof(T), instance);
}
return (T)instance;
}
public static int CountInstances { get { return instances.Count; } }
}
Ok… THIS works I can reveal. [Test]
public void
GenericSingletonContainerWithInternalStateClass_HasOneInstancePerGenericType()
{
Square square =
GenericSingletonContainerWithInternalStateClass<Square>.GetInstance();
Square square2 =
GenericSingletonContainerWithInternalStateClass<Square>.GetInstance();
Assert.IsNotNull(square);
Assert.IsNotNull(square2);
Assert.AreSame(square, square2);
Assert.AreEqual(1, GenericSimpleSingletonCollection<Square>.CountInstances);
}
[Test]
public void GenericSingletonContainerWithInternalStateClass_SharesMemorySpace_AsAllways()
{
GenericSingletonContainerWithInternalStateClass<Square>.GetInstance();
GenericSingletonContainerWithInternalStateClass<Triangle>.GetInstance();
Assert.AreEqual(2,
GenericSingletonContainerWithInternalStateClass<Square>.CountInstances);
Assert.AreEqual(2,
GenericSingletonContainerWithInternalStateClass<Triangle>.CountInstances);
}
I don't know if you noted in my code above that I also added the use of the [Test]
public void
GenericSingletonContainerWithInternalStateClass_CanCorrectlyUseImplicitOperator()
{
Square square =
GenericSingletonContainerWithInternalStateClass<Square>.GetInstance();
GenericSingletonContainerWithInternalStateClass<Square> singletonGetterClass =
new GenericSingletonContainerWithInternalStateClass<Square>();
Square square2 = singletonGetterClass;
Square square3 = new GenericSingletonContainerWithInternalStateClass<Square>();
Assert.AreSame(square, square2);
Assert.AreSame(square, square3);
Assert.AreEqual(1,
GenericSingletonContainerWithInternalStateClass<Square>.CountInstances);
Assert.AreEqual(1,
GenericSingletonContainerWithInternalStateClass<Triangle>.CountInstances);
}
Pretty cool huh? The only thing I have against this approach is that I declare ‘ The Trivial ComparisonIs this really such a revelation? Well I chose to show you this situation from the "code sample with a flaw"-end. Here’s the trivial test which explains the same from the other direction: public class GenericTrivialStaticField<T>
{
public static T Trivial { get; set; }
public static string AlsoTrivial { get; set; }
}
[Test]
public void TrivialTest()
{
GenericTrivialStaticField<string>.Trivial = "foo";
var bar = GenericTrivialStaticField<int>.Trivial;
Assert.AreNotEqual("foo", bar);
string foo = GenericTrivialStaticField<string>.Trivial;
Assert.AreEqual("foo", foo);
}
[Test]
public void AlsoTrivialTest()
{
GenericTrivialStaticField<string>.AlsoTrivial = "foo";
var bar = GenericTrivialStaticField<int>.AlsoTrivial;
Assert.AreNotEqual("foo", bar);
string foo = GenericTrivialStaticField<string>.AlsoTrivial;
Assert.AreEqual("foo", foo);
}
Here it becomes quite clear that the memory locations of ConclusionI was looking for a solution where I did NOT want to inherit any behavior and still get a shared state over all my closed generic types. This does not work! If this is the code you need you have to locate the shared state outside of the generic type definition. Even if you think you know how the language is constructed, you might still be surprised. Points of InterestEven if you are a samurai coder, you may get caught in mistakes like this one. The funny thing is that the code was developed using TDD and the behavior of the code was correct. Still the inner workings of the code underneath the surface were not what was expected! There was an actual bug in the code that misused memory in an unforeseen manner.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||