Introduction
UniqueStringList
is a class to store and manipulate lists of strings that must be unique. Strings can be added and
removed, inserted, sorted, looked up, compared to other lists etc. but duplicate strings will be simply ignored and will not
raise an exception.
Background
Whilst writing a VB6 Active Directory toolkit library for a client, I wrote a VB class called
UniqueStringCollection
to allow groups of case-insensitive strings, which had to be unique, to be stored together
and manipulated (for LDAP values/attribute names/list of DNs and the like).
I found this class to be useful in so many situations that when I started looking at C#, it was one of the first classes in my
utility library.
I decided to use the process of making a port of this class a backgrounder for myself in best .NET/C# practices such as
inheritance, interfaces, nested classes, class wrappers, serialization etc.
This article (my first, so go easy on me!) describes that ported version, since renamed to UniqueStringList
for
reasons we will see shortly.
Defining the Requirements
The basic requirements of my VB class were incorporated but expanded to make use of interfaces such as IList which would allow
better integration into the .NET framework hence the renaming to UniqueStringList
. These were my requirements:-
- Only one instance of a given string allowed in the list
- Duplicate strings are ignored with no exception thrown
- Lookups must be fast
- Nulls cannot be contained in the list (but no exception thrown)
- Implement
IList
/ICollection
/ISerializable
to be usable with other framework components - Make a case-sensitive version available
- Contents can be sorted/reversed etc.
- Include a flag to allow/disallow empty strings
- Include an option to produce a formatted summary list of the contents
- Make read-only and synchronized versions of the list available
How it Works
Although the existing .NET framework collections are comprehensive, no single type meets all of the requirements as specified
above. However, by combining an ArrayList
(for the IList
implementation) and a
Hashtable
(to provide quick lookups) all of the
above requirements can be met.
If you just need to have a collection of strings in no particular order then the Add()
and AddRange()
methods can be used to append strings to the list. Alternatively, if more control is required over the ordering of the strings,
then use Insert
which allows an index to be specified.
UniqueStringList
supports all of the usual framework collection list-type features including a read/write
indexer, IndexOf
, Clear
, Contains
, along with
ArrayList
type features such as
Sort
, Reverse
, ToArray
.
One feature that is not immediately obvious is the EmptyAlias
property. This is a read/write property that
get/sets a string to be used in place of an empty string. This is my solution to the requirement of allowing or disallowing
empty strings. By default, EmptyAlias
is itself an empty string and so an empty string will be allowed in the
list. By setting it to null, empty strings will be translated to nulls and so be ignored. You can also set it to an arbitrary
string and that string will be used in place of "" (I originally had a vague notion of using a UniqueStringList
as
a data source to a combo box and using this feature to supply a special value such as "(none)"). This seems to be a more useful
solution than just using a boolean flag.
The read-only version of the list is implemented as a wrapper class. This will allow a class to return a UniqueStringList
reference that is not externally modifiable but is 'live' and not just a snapshot copy. To create a read-only wrapper, call the
UniqueStringList.ReadOnly
static method passing a reference to the UniqueStringCollection
you want
to wrap.
The synchronized version of the list is obtained in the same way - call the static UniqueStringList.Sync
method.
Finally, for completeness, I added a case-sensitive version as a nested class. Use
UniqueStringList myCaseSensitiveList =
new UniqueStringList.CaseSensitive();
to create an instance.
Samples
Here is a simplistic snippet of code just to get a feel for the class - I'm sure you will find more interesting uses for it.
UniqueStringList myList = new UniqueStringList();
myList.Add("cat");
myList.AddRange(new string[] {"dog", "parrot"});
Console.WriteLine(myList.ToString("My pets: ", "; ", "."));
A bit boring? Well OK, here's another more realistic sample:
string[] mandatoryAttributes = new string[] {
"objectClass", "objectCategory"
};
UniqueStringList attributeNames =
new UniqueStringList(mandatoryAttributes);
foreach(CustomFilter filter in filters) {
attributeNames.Add(filter.RequiredAttributes);
}
Points of Interest
Check out in the source code how the wrapper classes call an internal constructor with a superfluous parameter (just used
to differentiate from the other constructors). This allows the wrapper class to be created without initializing its own data fields to save memory though I must confess I found this idea from the Rotor source code.
By allowing additional formatting parameters on the ToString()
method override, it is easy to make summary lists
using commas or semi-colons or CRLF combinations etc.
myList.ToString("[", "; ", "]");
myList.ToString("", "\n", "");
Sometimes, it can be handy to see the status of a class during debugging. By creating a property that is only compiled into a
debug build, you can have the benefit of seeing a summary in the watch window whilst debugging with no overhead in a release
build.
#if DEBUG
public string DebugWatch {
get { return ToString(); }
}
#endif
Future Enhancements
- Add class and method documentation - need to check out NDoc which I hear is excellent.
History
- v1.00 22/11/02 - First release to CodeProject.
Simon Hewitt is a freelance IT consultant and is MD of Hunton Information Systems Ltd.
He is currently looking for contract work in London.
He is happily married to Karen (originally from Florida, US), has a lovely daughter Bailey, and they live in Kings Langley, Hertfordshire, UK.