Hunting down memory leaks in Silverlight
Recently, I was noticing some memory issues with a Silverlight application I was working on. I was creating a dialog window (user control) dynamically where the user could edit and save data. The dialog user control was data bound to a ‘ViewModel’ type which populates the data controls in the dialog and is then used to populate and save a business object. After, the user clicks on the OK or the Cancel button, i was disposing away with the user control from memory by setting its reference to NULL. But, when i repeated the process of opening and closing the dialog multiple times, the memory used by Internet Explorer always kept going up. This made me suspicious that even though I was setting the user control’s reference to NULL, the user control was never disposed away from memory.
To confirm that multiple instances of the user control were still in memory, I resorted to using “windbg”. Following the steps below, you can confirm and debug memory leaks in Silverlight and also in the .NET CLR.
- Download and install windbg from here.
- Run the Silverlight application and bring it to a state where you think a memory problem exists. In my case above, I had to open and close the suspect dialog multiple times.
- Run windbg and attach to the Internet Explorer process running your Silverlight application.
- Load the SOS debugging extension for Silverlight. This is installed with the Silverlight SDK on your system. The ‘SOS.dll’ is found under the Silverlight installation directory. For me it is at “C:\Program Files\Microsoft Silverlight\2.0.40115.0\sos.dll”. To load the SOS debugging extension execute the command “.load C:\Program Files\Microsoft Silverlight\2.0.40115.0\sos.dll” in the windbg window.
- Run the “!dumpheap -stat” command to view the objects in memory and the count of their instances.
- Run the “!dumpheap –stat –type [TypeName]” command to view the number of instances of the specified type in memory. In my case, I found that the number of instances of the dialog user control object in memory was always equal to the number of times I had opened and closed the dialog user control. This indicated that the user control object were not getting disposed from memory even when I had set its reference to NULL.
- Run the “!dumpheap –type [TypeName]” command to view all the memory addresses of the object’s instances.
- Run the “!gcroot [Address]” command to view how the instance of the object can be reached. In other words, it tells us what is holding on to the reference of the object and thus keeping the object in memory without being cleaned up by the garbage collector. The output here is hard to read, but the pain of going through this exercise will definitely pay off when the memory leak in the application has been fixed.
In my case, it turned out that the ‘ViewModel’ object the dialog user control was being data bound to was holding on to a reference to the user control object. So, to fix the memory leak, all I had to do was to set the ‘DataContext’ property of the user control to NULL before setting the user control reference itself to NULL.
Automated Build Starter Kit
Keeping with the automated build topic like the last two posts, you can download an “Automated Build Starter Kit” here (click image below)
The goal of this starter kit is to enable to quickly setup an automated build for any of your .NET projects using NAnt. To get started with the kit, all you need to do is unzip the contents of the zip file and rename the folder to something like the name of the project. And that’s it you are done. You can build the sample project in the zip file by opening up the command prompt at that directory location and execute the command “go build”. The zip file also contains a sample web application and a sample web site in it. To publish the sample web application to the IIS on the current machine execute “go publishWebApp” and to publish the sample website to the IIS on the current machine execute “go publishWebSite”. To publish both the sample web application and web site, execute “go publish”. The build script automatically registers the published locations as virtual directories with IIS also. Please the ReadMe.txt file in the zip contents for more information.
Launching your automated build scripts from within Visual Studio
In my last post, I had talked about the benefits of using a common directory structure for all your projects. Well here is another good one. I used to be opening up my command prompt and changing directories to the root directory of the project to launch my build scripts every time I needed to build. Opening up the command prompt to launch the build can get old on you pretty soon. But I was able to add a couple of buttons to my Visual Studio toolbar to launch my automated build and deploy scripts eliminating the need for the command prompt. Adding commands to the Visual Studio toolbar is pretty easy and can be done as shown below. Go to the “Tools -> External Tools” item on the Visual Studio menu bar.
The batch file I use to launch my automated build is always called “Go.bat” and resides at the root of the project directory and its contents look like this
@tools\nant\NAnt.exe -buildfile:MyProject.build %*
So, I just need to launch the “Go.bat” file with the project root as the working directory with the right NAnt targets as arguments. Since, my “Go.bat” file and my Visual Studio solution file reside in the same directory (project root). I can setup my external commands as below.
Make sure to check the “Use Output Window” option so that you can watch the output from your build script in your Visual Studio output window. Cool huh…
I can setup two external tools that invoke the “Go.bat” file with the working directory set to the path where the Visual Studio solution file ($(SolutionDir)/) resides. Note that I am passing in “build” and “publish” as the arguments to the “Go.bat” file when I launch it to either run the “build” NAnt target or run the “publish” NAnt target. Once the two entries have been setup here, they will appear in the “Tools” menu options in Visual Studio.
The last thing that is left to do is the trickiest part and I am not sure why Microsoft has left it this way. Go to “Tools -> Customize” and the dialog below should pop-up. Select “Tools” in the “Categories” list on the left. And this should list the externals commands you just added on the right. But the strange part is that the externals tools you setup will not be listed with the name you set them up with but with just general names like “External Command 2″ & “External Command 3″ etc. The correct names will not show up until you drag the tools from the dialog and drop them into one of your Visual Studio toolbars. Weird… but it works. I had a new toolbar setup for these two buttons and dropped them on to there.
So, I am happily building and deploying my projects with a single click from within Visual Studio.
Project Directory Structure
The directory structure that is used for projects could either make your life a whole lot easier or make it a pain when you are setting up your automated builds. Apart from being a crucial part of my automated build workflow, it also helps me makes sure that I can check out the project on a new machine and have the project be build-able right away both with the build script and in Visual Studio. Here is the project directory structure that I have been using for a while now. I have been very satisfied with it. Since I use the same project structure for all my projects, I can use a template build script that I can use right away by changing a couple of values in it.
What directory structure do you use for your projects? Thoughts? Suggestions?
Inserting spatial data in SQL Server 2008
I have been working on inserting data from FeatureClasses into SQL Server 2008 tables. There are a lot of examples online showing how spatial data can be inserted into SQL Server 2008 tables. Here is one from Hannes.
I took from cue from there and proceeded to do what every good programmer should do…parameterize the INSERT query. Here is a simplified version of the scenario I was trying to tackle…
sqlCommand.CommandText = INSERT INTO foo (id, geom) VALUES(1,@g);
sqlCommand.InsertParameter(“@g”, ‘POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))’);
The above INSERT command worked fine. But I also wanted to insert the geometry or geography with the WKID value from the FeatureClass. So I changed the above INSERT statement to use the geometry’s static method and provided the WKID as below
sqlCommand.InsertParameter(“@g”, ‘geometry::STGeomFromText(POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0)), 0)’);
But that didn’t go quite as expected. The INSERT statement was throwing an error saying that it did not recognize ‘geometry::STGeomFromText’ and was expecting ‘POINT, POLYGON, LINESTRING…’ etc.
System.FormatException: 24114: The label geometry::STGeomFrom in the input well-known text (WKT) is not valid. Valid labels are POINT, LINESTRING, POLYGON, MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, or GEOMETRYCOLLECTION.
This led me to use the Udt SqlParamter type to get around the problem like below. This way I could use the SqlGeometry type in the ‘Microsoft.SqlServer.Types’ namespace and set the SRID directly on its ‘STSRID’ property. The ‘SqlGeometry’ and ‘SqlGeography’ types can be found in the Feature Pack for SQL Server 2008 that can be found here. The SqlGeometry reference can then be used as the value for the SqlParameter.
SqlParameter parameter = new SqlParameter( parameterName, value );
parameter.Direction = ParameterDirection.Input;
parameter.ParameterName = parameterName;
parameter.SourceColumn = sourceColumn;
parameter.SqlDbType = SqlDbType.Udt;
parameter.UdtTypeName = “GEOMETRY”;
parameter.SourceVersion = DataRowVersion.Current;command.Parameters.Add( parameter );
The ‘UdtTypeName’ should be set to ‘GEOGRAPHY’ for inserting geography fields.
And it worked like a charm.
Update: Heard from Morten saying that using the SqlGeography and SqlGeometry types are almost twice as fast as using WKB (Well Known Binary). Using WKB is by itself faster than using WKT (Well Known Text).
Simulating properties on a bindable list using ITypedList
In this blog post I am going to illustrate how to simulate properties on a bindable list using the ITypedList interface. The ITypedList interface is used by the .NET data-binding mechanism to discover the properties of the items in the list that is being bound. So in fact the ITypedList interface can be implemented by custom collections to customize the properties available for binding on the object to bind to. The .NET data-binding mechanisms use the GetItemProperties(…) method on the ITypedList interface to obtain a PropertyDescriptorCollection to obtain the list of properties available on the object in the list. So, the GetItemProperties(…) method can be overridden to return a custom collection of properties that will be used for data-binding.
Let us take for example, the ‘Distance’ class given below.
public class Distance
{
double _tolerance = 0;
esriUnits _units = esriUnits.esriFeet;
public Distance(){}
public Distance(double ptolerance, esriUnits punits)
{
_tolerance = ptolerance;
_units = punits;
}
public double Tolerance
{
get { return _tolerance; }
set { _tolerance = value; }
}
public esriUnits Units
{
get { return _units; }
set { _units = value; }
}
}
The ‘Distance’ class above contains a tolerance measure and its units as it is public properties. Let’s say that we also have a typed collection ‘DistanceCollection’ of the ‘Distance’ objects.
public class DistanceCollection
{
#region “CollectionBase Members”
public Distance this[int index]
{
get
{
return ((Distance)List[index]);
}
set
{
List[index] = value;
}
}
public int Add(Distance value)
{
return (List.Add(value));
}
public int IndexOf(Distance value)
{
return (List.IndexOf(value));
}
public void Insert(int index, Distance value)
{
List.Insert(index, value);
}
public void Remove(Distance value)
{
List.Remove(value);
}
public bool Contains(Distance value)
{
// If value is not of type Distance, this will return false.
return (List.Contains(value));
}
protected override void OnInsert(int index, Object value)
{
// Insert additional code to be run only when inserting values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnRemove(int index, Object value)
{
// Insert additional code to be run only when removing values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnSet(int index, Object oldValue, Object newValue)
{
// Insert additional code to be run only when setting values.
if (oldValue.GetType() != typeof(Distance))
throw new ArgumentException(“oldValue must be of type Distance.”, “value”);
if (newValue.GetType() != typeof(Distance))
throw new ArgumentException(“newValue must be of type Distance.”, “value”);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
#endregion
}
A new datasource could be added in Visual Studio for the ‘DistanceCollection’ type as shown below…
Our goal is to simulate a property called ‘ToleranceInMeters’ on the ‘DistanceCollection’ type so that we have an additional column on the DataGridView specifying the tolerance in meters. In order to achieve this we implement ITypedList on the ‘DistanceCollection’ type and override the GetItemProperties(…) method so that it returns a PropertyDescriptorCollection object with an added PropertyDescriptor object for the ‘ToleranceInMeters’ property.
To return an additional property in the PropertyDescriptorCollection that returns the tolerance in meters, we create a new class called ‘ToleranceInMetersPropertyDescriptor’. And in the GetValue(…) method of the new class we implement the logic to convert the tolerance value from the existing units into meters. In the below code, the ‘ToleranceInMetersPropertyDescriptor’ class has been implemented as an inner class inside ‘DistanceCollection’. We also build the new PropertyDescriptorCollection in the static constructor of ‘DistanceCollection’ and return it inside the GetItemProperties(…) method.
public class DistanceCollection : CollectionBase, ITypedList
{
static DistanceCollection()
{
CalculatePropertyDescriptors();
}
#region “CollectionBase Members”
public Distance this[int index]
{
get
{
return ((Distance)List[index]);
}
set
{
List[index] = value;
}
}
public int Add(Distance value)
{
return (List.Add(value));
}
public int IndexOf(Distance value)
{
return (List.IndexOf(value));
}
public void Insert(int index, Distance value)
{
List.Insert(index, value);
}
public void Remove(Distance value)
{
List.Remove(value);
}
public bool Contains(Distance value)
{
// If value is not of type Distance, this will return false.
return (List.Contains(value));
}
protected override void OnInsert(int index, Object value)
{
// Insert additional code to be run only when inserting values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnRemove(int index, Object value)
{
// Insert additional code to be run only when removing values.
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
protected override void OnSet(int index, Object oldValue, Object newValue)
{
// Insert additional code to be run only when setting values.
if (oldValue.GetType() != typeof(Distance))
throw new ArgumentException(“oldValue must be of type Distance.”, “value”);
if (newValue.GetType() != typeof(Distance))
throw new ArgumentException(“newValue must be of type Distance.”, “value”);
}
protected override void OnValidate(Object value)
{
if (value.GetType() != typeof(Distance))
throw new ArgumentException(“value must be of type Distance.”, “value”);
}
#endregion
#region “Simulated Property”
private class ToleranceInMetersPropertyDescriptor : PropertyDescriptor
{
UnitConverterClass _converter = new UnitConverterClass();
public ToleranceInMetersPropertyDescriptor(string name)
: base(name, null)
{
}
public override bool CanResetValue(object component)
{
return false;
}
public override System.Type ComponentType
{
get { return typeof(Distance); }
}
public override bool IsReadOnly
{
get { return true; }
}
public override System.Type PropertyType
{
get { return typeof(double); }
}
public override void ResetValue(object component)
{
}
public override object GetValue(object component)
{
Distance d = (Distance)component;
return _converter.ConvertUnits(d.Tolerance, d.Units, esriUnits.esriMeters);
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
#endregion
#region ITypedList Members
private static PropertyDescriptorCollection propertyDescriptors;
PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
{
return propertyDescriptors;
}
string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
{
return “DistanceCollection”;
}
private static void CalculatePropertyDescriptors()
{
PropertyDescriptorCollection origProperties = TypeDescriptor.GetProperties(typeof(Distance));
ArrayList properties = new ArrayList();
foreach (PropertyDescriptor desc in origProperties)
{
properties.Add(desc);
}
properties.Add(new ToleranceInMetersPropertyDescriptor(“ToleranceInMeters”));
propertyDescriptors = new PropertyDescriptorCollection((PropertyDescriptor[])properties.ToArray(typeof(PropertyDescriptor)));
}
#endregion
}
What do you achieve by all this? Well, we get a property on the ‘DistanceCollection’ type in the Data Sources window in Visual Studio 2005 and when it is dragged on to the form the DataGridView now appears with a an additional column called ‘ToleranceInMeters’.
In the constructor of the form, initialize the binding source for the DistanceCollection and the result will be what you see below.
DistanceCollection _Coll = new DistanceCollection();
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriFeet));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriInches));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriKilometers));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriMiles));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriNauticalMiles));
_Coll.Add(new Distance(1, ESRI.ArcGIS.esriSystem.esriUnits.esriDecimalDegrees));
distanceCollectionBindingSource.DataSource = _Coll;
Comments on Microsoft Enterprise Library Exception Block
We have been using the “Microsoft Enterprise Library Exception Block” for exception handling and logging in the current Enterprise Information management Systems that I am working on. Based on my experience with the “Microsoft Enterprise Library Exception Block“, I had a couple of comments that wanted to put out there. These observations of mine may just be complaints about the library that I wish could have been circumvented. But I do agree that the library does provide us with a powerful tool to standardize the way exceptions are handled throughout the application. For those who haven’t used the Exception block library before, here is a quick overview. I feel that this is a great one line summary of the Exception block. “The Exception block separates the definition how an exception should be processed from the application code itself“.
Exception Handling Application Block
It is a framework for handling exceptions (catching, presentation to the user, logging, etc.) in a recommended and standardized way. It allows developers and policy makers to create a consistent strategy for processing exceptions that occur throughout the architectural layers of enterprise applications. It does this in the following ways:
- It supports exception handling throughout an application’s architectural layers and is not limited to service interface boundaries.
- It enables exception-handling policies to be defined and maintained at the administrative level so that policy makers, who may be system administrators as well as developers, can define how to handle exceptions. They can maintain and modify the sets of rules that govern exception handling without changing the application block’s code.
- It provides commonly used exception-handling functions, such as the ability to log exception information, the ability to hide sensitive information by replacing the original exception with another exception, and the ability to add contextual information to an exception by wrapping the original exception inside of another exception. These functions are encapsulated in .NET classes called exception handlers.
- It can combine exception handlers to produce the desired response to an exception, such as logging exception information followed by replacing the original exception with another.
- It lets developers create their own exception handlers.
- It invokes exception handlers in a consistent manner. This means that the handlers can be used in multiple places within and across applications.“
(ms-help://MS.VSCC.2003/ms.EntLib.2005Jun/ms.EntLib.2005Jun.Ex/ExceptionHandling/Intro/intro01.htm)
Architecture
The Exception block separates the definition of how an exception should be processed from the application code itself and moves this configuration to a XML config file. The configuration of the Exception block uses following terms:
Policy
A Policy has a name and a set of exception types that should be processed. An application can have several policies, because it could be necessary to have different processing instructions for one exception type, depending in which layer of the application the exception arises.Exception Type
For each policy one or more exception types can be defined. Exception types can be one of the standard .net exception classes or user defined ones. When an exception is thrown the exception block will determine the type of the exception against the definitions in the used policy and call the exception handlers of this type.Exception handlers
Exception handlers process the exception. An exception type can have one or more handlers. They are called by the application block in the order like they were configurated.
There are following built-in handlers:
- Wrap handler: Creates a new exception and sets the original exception as inner exception for the new one.
- Replace handler: Replaces an exception with a new (custom) one.
- Logging handler: Logs the exception to the Enterprise application logging block, which can be used to log events to different targets like event log, files, databases etc.
- Custom handlers: Developers can write their own exception handlers which can be easily included in the project (see “Creating a custom exception handler”.
The following figure illustrates examples of cross-layer and single-application component exception handling:
(ms-help://ms.EntLib.2005Jun/ms.EntLib.2005Jun.Ex/exceptionHandling/Intro/whentouse03.htm)
The figure above shows how the “UI Layer (UI)”, “Business Components Layer (BCL)” and the “Data Access Layer (DAL)” normally handle exceptions. Any exceptions that occur in the DAL library should be logged and then wrapped with some meaningful contextual information on where and possibly why the exception happened and passed up to the calling BCL library. The BCL library handles the exception by logging it and if necessary replacing it with an another exception that makes more sense when finally handled by the UI layer and displayed to the user.
The policy that should handle the exception is specified by the developer in the ExceptionPolicy.HandleException(…) method as shown below.
try
{
//…
}
catch (Exception e)
{
bool rethrow = ExceptionPolicy.HandleException(e, “exception policy name”);
if (rethrow)
{
throw;
}
}
Ideally, I think that there should be some kind of restrictions on the type of policies that can be specified in the parameter for the ExceptionPolicy.HandleException(…) method. By that I mean, for example the “Notify User” policy should never be used in the DAL or the BCL.
My other comment about the above code is that, the developer is now responsible for handling the “rethrow” boolean value returned by the ExceptionPolicy.HandleException(…) method and explicitly throwing the exception again. The rule to throw exception again is not encapsulated in the policy itself. Also, the decision to throw the exception again is based on the exception that has occurred. Some exceptions may need to be thrown again but some others need not. Given that most programmers are lazy, there is a real good chance that the exceptions do not thrown again. This can lead to situations where exceptions get swallowed without proper user notification if required. The kind of errors can be hard to track down because there may not be a user notification that some thing has gone wrong and can cause problems in parts of the system that rely on it. Maybe some custom FxCop rules can be checked to catch these errors before they move into production.
Static constructors in generic types
Static constructors are a relatively little known feature .NET. It might probably be because they don’t have too many use cases where they fit in perfectly. But they did come in handy when I was looking for ways for make a list of all the properties in my class that were decorated in with a custom attribute. The static constructor was an ideal place to build the collection so that the collection can be exposed as a static property on my class.
But I did have my doubts on whether I will be able to use the static constructors effectively on generics types. Generics types are tricky when it comes to static constructors since the generic type can be customized with other type parameters. But turns out the static constructors do work effectively on generic types. The type parameters are available in the static constructor and the type parameter T in the static constructors for CustomType<int> and CustomType<string> refer to their corresponding type parameters even though they use the same CustomType<T> class.
The use of static constructors can be observed in the project attached to one of my previous blog post.
Persisting ESRI objects to the database
A little while ago, I was trying to persist some objects implementing the IPersistStream to the database. The database was MS SQL Server 2005 and the field type where the object was going to be stored was VARBINARY(MAX). The field was exposed in the DAL as an Byte array like it is supposed to be. So, now I had to write my objects implementing IPersistStream into an byte array. This took me a little while to figure out but turned to be simple enough. When, I had to do this before, I was writing to a table in a personal geodatabase. This was pretty straightforward since I was accessing and updating the storage table using ESRI’s ITable interfaces. In that case, I simply serialized my objects to a MemoryBlobStream and the wrote the MemoryBlobStream object into the table using the “Value” property on IRow. But this wouldn’t work for the current MS SQL server tables since I wasn’t accessing in through the ITable interface. So, I HAD to convert my MemoryBlobStream into an byte array. This is where the IMemoryBlobStreamVariant interface comes in handy. The following is how I was able to convert the MemoryBlobStream object to an byte array.
Dim s As MemoryBlobStream = New MemoryBlobStream()
Dim os As IObjectStream = New ObjectStream()
os.Stream = TryCast(s, ESRI.ArcGIS.esriSystem.IStream)
pitem.Save(os, 0)
Dim content As Object = Nothing
Dim mvar As IMemoryBlobStreamVariant = TryCast(s, IMemoryBlobStreamVariant)
mvar.ExportToVariant(content)
Dim bContent As Byte() = CType(content, Byte())
Databinding Enum values to combo boxes
Even though I dislike the use of enumerations, there are cases where you can use them to make your life a lot easier. In those cases, there might be a requirement to have the enumeration property data bound to a combo box which lists all possible values of the enumeration in some meaningful text and also be able to select the right enumeration text when the object is bound to it. I have written a small little utility that will do just that. It returns a Read-Only collection with some meaningful text as the “Name” and the enum value as the “Value” member of the collection. In the sample, the generic class EnumCollection<T> has a static DataSource property that returns a ReadOnlyICollection that is data bindable to a combo box. The type T represents an enumeration type. The text to describe the enumeration values can be added as custom attribute decorators in the definition of the enumeration as below.
1 public enum EnumSample
2 {
3 [Text("This is One.")]
4 One,
5 [Text("This is Two.")]
6 Two,
7 [Text("This is Three.")]
8 Three,
9 [Text("This is Four.")]
10 Four
11 }
The EnumCollection<T> class creates its collection in a static constructor for the generic class and thus is able to return the required collection in a static “DataSource” property. The definition of the EnumCollection<T> class is below.
1 public class EnumCollection<T> : ReadOnlyCollection<EnumClass<T>>
2 {
3 static EnumCollection<T> _val;
4
5 static EnumCollection()
6 {
7 if (!typeof(T).IsEnum)
8 throw new InvalidCastException(“sbEnumCollection only supports ‘enum’ types.”);
9
10 List<EnumClass<T>> vals = new List<EnumClass<T>>();
11 Array values = Enum.GetValues(typeof(T));
12 foreach (Object item in values)
13 {
14 EnumClass<T> entry = new EnumClass<T>(GetText<T>(item), (T)item);
15 vals.Add(entry);
16 }
17 _val = new EnumCollection<T>(vals);
18 }
19
20 private static string GetText<U>(object item) where U : T
21 {
22 if (!typeof(U).IsEnum)
23 throw new InvalidCastException(“GetText only supports ‘enum’ types.”);
24
25 FieldInfo fi = typeof(U).GetField(item.ToString());
26 string result = null;
27 if (fi != null)
28 {
29 object[] attrs = fi.GetCustomAttributes(typeof(TextAttribute), true);
30 if (attrs != null && attrs.Length > 0)
31 result = ((TextAttribute)attrs[0]).Text;
32 }
33
34 if (result == null)
35 result = Enum.GetName(typeof(T), item);
36
37 return result;
38 }
39
40 internal EnumCollection(IList<EnumClass<T>> list) : base(list) { }
41
42 public static EnumCollection<T> DataSource
43 {
44 get
45 {
46 return _val;
47 }
48 }
49 }
Enumerations can be data bound to combo boxes easily as shown below.
1 comboBox1.DataSource = EnumCollection<EnumSample>.DataSource;
2 comboBox1.DisplayMember = “Name”;
3 comboBox1.ValueMember = “Value”;
The complete project can be downloaded here.


Follow Me
Contact me