A SortedSet in C# is a data structure that allows us to store and access elements in sorted order. In this article, we are going to explore how to create and use the SortedSet class in C#. We are also going to look at some of the benefits and drawbacks of using this data structure.

Finally, the article concludes with tips on taking advantage of the C# SortedSet in our applications.

To download the source code for this article, you can visit our GitHub repository.

Without further ado, let’s begin!

What Is a SortedSet in C#?

A SortedSet in C# is a collection of unique elements sorted according to their natural ordering or a specified comparator. It allows for fast retrieval of elements and efficient operations on subsets.

The SortedSet<T> class has been available since .NET as part of the System.Collection.Generic namespace.

One notable feature of the SortedSet is its ability to efficiently perform set operations such as union, intersection, and difference, which makes it particularly useful for tasks such as creating a list of unique elements from multiple sets.

SortedSet does not allow null values or permit duplicate elements. Trying to add a duplicate element will not affect the set. 

How to Create a SortedSet in C#?

For most of our examples, we intend to use a SortedSet that contains strings of some of the programming languages used today.

Let’s start by creating an empty SortedSet:

var languages = new SortedSet<string>();

SortedSet Constructor Overloads

We can use these overloads when working with the SortedSet class in C#:

OperationHashSetSortedSet
IterationO(N)O(N)
SearchO(1)O(N)
InsertionO(1)O(Log N)
Removing ElementsO(1)O(Log N)
Enumerating Elements in Sorted OrderO(N Log N)O(N)

To make our article as simple as possible, we are going to implement the first overload, SortedSet() and learn how it works. 

Add Items to a SortedSet

Let’s add a set of programming languages into a SortedSet<string> object:

public SortedSet<string> ProgrammingLanguages() 
{ 
    var languages = new SortedSet<string>(); 

    languages.Add("C"); 
    languages.Add("C++"); 
    languages.Add("C#"); 
    languages.Add("Java"); 
    languages.Add("Scala"); 
    languages.Add("TypeScript"); 
    languages.Add("Python"); 
    languages.Add("JavaScript"); 
    languages.Add("Rust");
    
    return languages;
}

Here, we insert elements into the languages SortedSet by invoking the Add() method.

In some cases, we may want to initialize a  SortedSet directly with values:

var languages = new SortedSet<string> { "C", "C++", "C#", "Java" };

Let’s initialize our SortedSet in the test constructor:

private readonly SortedSetMethods _sortedSet; 
private readonly SortedSet<string> _languages;

public SortedSetInCSharpUnitTests()
{
    _sortedSet = new SortedSetMethods();
    _languages = _sortedSet.ProgrammingLanguages();
}

Next, we can verify that our SortedSet has nine elements and check whether it contains one of the elements (“Java”) and the first element is (“C”):

Assert.IsInstanceOfType(_languages, typeof(SortedSet<string>));
Assert.AreEqual(_languages.Count(), 9);
Assert.IsTrue(_languages.Contains("Java"));
Assert.AreEqual(_languages.First(), "C");

We can successfully prove that _languages is of type SortedSet<string>. We use the inbuilt Count() method to check the number of elements in our SortedSet.

Also, we use the inbuilt Contains() method to check if the SortedSet has a specific element. The method takes the element as a parameter and returns a boolean value indicating whether or not the element is present in the set.

This technique can be helpful for quickly checking if an element exists in the SortedSet without iterating through all of the elements.

Add Duplicate Elements in a SortedSet in C#

A SortedSet does not allow adding duplicate elements. It will not affect the set if we try to add a duplicate element. It uses a hashing structure that ensures that each element can only appear once in the set.

Let’s attempt to add duplicate elements to the _languages SortedSet:

_languages.Add("C");
_languages.Add("C++");
_languages.Add("C#");

Assert.IsInstanceOfType(_languages, typeof(SortedSet<string>));
Assert.AreEqual(_languages.Count(), 9);

When we try to add duplicate values to _languages, its values will not change, as the set already contains those values. Therefore, its count remains nine instead of twelve.

How Does a SortedSet Deal With Object Sorting?

To achieve object sorting in a SortedSet<T> object, the SortedSet<T> class has a SortedSet<T>.Comparer property that ensures all the elements in the object are in the correct order. The property implements the IComparer<T> interface, which compares elements and ensures that the SortedSet<T> elements are in the correct order. 

In our case, the SortedSet<T> class uses the default comparer to ensure that we store our programming languages in the correct order.

Iterate Through a SortedSet in C#

We can iterate through a SortedSet object by using a foreach or a for loop statement.

Let’s invoke the languages SortedSet in our main program and iterate through it:

var sortedSet = new SortedSetMethods();
var programmingLanguages = sortedSet.ProgrammingLanguages();

Console.WriteLine("The SortedSet Contains these elements:");

foreach (var language in programmingLanguages) 
{
    Console.WriteLine(language);
}

After executing the program, we can see that our programming languages are in the correct alphabetic order:

The SortedSet Contains these elements:
C
C#
C++
Java
JavaScript
Python
Rust
Scala
TypeScript

Remove All Elements From a SortedSet in C#

What if we want to remove all the elements in a SortedSet? We can make use of the Clear() inbuilt method.

Let’s verify that the Clear() method removes all elements from the _languages set:

_languages.Clear();

Assert.AreEqual(0, _languages.Count());
Assert.IsNull(_languages.FirstOrDefault());

After invoking the Clear() method, we can prove that we removed all the elements from the _languages set as it has a count of zero.  

Copy SortedSet Elements to an Array

The CopyTo() method copies a part of or the entire SortedSet into a one-dimensional array of the same type. The method has some overloads, which give us an option of copying elements at the beginning of the destination array or from a specific index.

To keep our example simple, let’s copy all the elements in the _languages SortedSet into an array by taking advantage of the CopyTo() method:

var languagesArray = new string[9];

_languages.CopyTo(languagesArray);

Assert.AreEqual(languagesArray.Count(), 9);
Assert.AreEqual(languagesArray[0], "C");
Assert.AreEqual(languagesArray[8], "TypeScript");
Assert.IsNotNull(languagesArray);

Alternatively, we can convert a SortedSet into an array by invoking the ToArray() method:

var languagesArray = _languages.ToArray();

Perform Set Difference Between Two SortedSets in C#

This operation performs a set difference operation between two sets. If we perform a set difference between sets A and B, the operation returns the elements in A that are not present in B.

Let’s understand this concept with another example:

var moreLanguages = new SortedSet<string> { "C", "C++", "C#", "Java", "Scala", "Assembly", 
                    "Pascal", "HTML", "CSS", "PHP" };

_languages.ExceptWith(moreLanguages);

Assert.AreEqual(_languages.Count(), 4);
Assert.IsTrue(_languages.Contains("TypeScript"));
Assert.IsTrue(_languages.Contains("Python"));
Assert.IsTrue(_languages.Contains("JavaScript"));
Assert.IsTrue(_languages.Contains("Rust"));
Assert.IsFalse(_languages.Contains("Assembly"));

The ExceptWith() method returns the elements that are in _languages but not in moreLanguages, which are: “TypeScript”, “Python”, “JavaScript” and “Rust”. 

Find Common Elements Between Two SortedSets

An intersection between sets A and B entails finding the common elements. To accomplish such an operation in C#, we use the inbuilt IntersetWith() method. 

Let’s understand how to perform an intersection operation with an example:

var moreLanguages = new SortedSet<string> { "C", "C++", "C#", "Java", "Scala", "Assembly", 
                    "Pascal", "HTML", "CSS", "PHP" };

_languages.IntersectWith(moreLanguages);

Assert.AreEqual(_languages.Count(), 5);
Assert.IsTrue(_languages.Contains("C"));
Assert.IsTrue(_languages.Contains("C++"));
Assert.IsTrue(_languages.Contains("C#"));
Assert.IsTrue(_languages.Contains("Java"));
Assert.IsTrue(_languages.Contains("Scala"));
Assert.IsFalse(_languages.Contains("Assembly"));

Here, the IntersectWith() method modifies the _languages SortedSet by retaining the elements that are common in both _languages and moreLanguages sets. 

How to Check A SortedSet Is a Subset of Another

When we want to check whether a SortedSet instance is a proper subset of another SortedSet instance, we use the IsProperSubsetOf() method. Likewise, we can use the IsProperSupersetOf() method to determine if a SortedSet is a superset of another SortedSet.

Let’s put this theory into practice:

var moreLanguages = new SortedSet<string> {"C", "C++", "C#", "Java", "Scala", "TypeScript", 
                    "Python", "JavaScript", "Rust", "Assembly", "Pascal"};

Assert.IsTrue(_languages.IsSubsetOf(moreLanguages));
Assert.IsTrue(_languages.IsProperSubsetOf(moreLanguages));
Assert.IsTrue(moreLanguages.IsSupersetOf(_languages));
Assert.IsTrue(moreLanguages.IsProperSupersetOf(_languages));

We create a larger set moreLanguages that contains more elements, including all the elements in the _languages set. Therefore, _languages is a proper subset of moreLanguages and the latter is the proper superset of the former. 

So, what’s the difference between a proper subset and a normal one? 

In set theory, a subset is a collection of elements within another set. A proper subset, also known as a strictly smaller subset, is a subset that contains strictly fewer elements than the larger set. 
A proper subset will always have fewer elements than the larger set, which may not be true for a subset. For example, if we have the set A = {1, 2, 3}, B = {1, 2} is a proper subset because it contains strictly fewer elements than A. On the other hand, C = {1, 2, 3} would not be considered a proper subset because it has the same number of elements as A.

Determine Whether Two SortedSets Overlap

In some situations, we may want to check whether two SortedSet objects share common elements without evaluating whether they are equal. That’s where we can take advantage of the Overlaps() method to achieve our objective:

var commonLanguages = new SortedSet<string> { "C", "C++", "C#", "Java", "Scala", "TypeScript", 
                      "Python", "JavaScript", "Rust", "Assembly", "Pascal" };
var differentLanguages = new SortedSet<string> { "Assembly", "Pascal", "HTML", "CSS", "PHP" };

Assert.IsTrue(commonLanguages.Overlaps(_languages));
Assert.IsTrue(differentLanguages.Overlaps(commonLanguages));
Assert.IsFalse(differentLanguages.Overlaps(_languages));

We can see that the commonLanguages and _languages SortedSets have common elements. The same case applies to differentLanguages and commonLanguages. However, differentLanguages and languages do not have overlapping elements. 

Remove a Particular Element From a SortedSet

To remove an item from the SortedSet, we can use the Remove() method. Like the Add() method, it takes the object as a parameter and removes it from the SortedSet.

Let’s put our knowledge into practice: 

public SortedSet<string> RemoveElement(SortedSet<string> sortedSet, string valueToRemove) 
{
    _sortedSet.Remove(valueToRemove);

    return sortedSet;
}

Here, the RemoveElement() method takes a SortedSet<string> and a string as parameters and uses the Remove() method to remove an element before returning the updated SortedSet

Next, we can verify that our new method successfully removes elements:

var elementToRemove = "Java";

var updatedLanguages = _sortedSet.RemoveElement(_languages, elementToRemove);

Assert.IsFalse(updatedLanguages.Contains(elementToRemove));
Assert.AreEqual(_languages.Count(), 8);

We invoke the RemoveElement() method and pass our SortedSet, and a string (“Java”), which we can prove is removed as the updated SortedSet does not contain that value, and its count decreases by one. 

Remove Elements From a SortedSet Through Predicates

Besides using the inbuilt Remove() method, we can use the RemoveWhere() method that takes a predicate as a parameter to set conditions that determine whether we remove an element. To illustrate this concept, let’s modify our _languages SortedSet to remove programming languages that start with a specific character:

_languages.RemoveWhere(element => element.StartsWith("C"));
Assert.IsFalse(_languages.Contains("C"));
Assert.IsFalse(_languages.Contains("C++"));
Assert.IsFalse(_languages.Contains("C#"));

We can verify that the RemoveWhere() method removes all elements starting with the letter ‘C.’

Reverse Elements of a SortedSet in C#

In some cases, we may want to reverse the order of the elements we have in a SortedSet. We can take advantage of the inbuilt Reverse() method to achieve our goal. The method returns an IEnumerable<T> object, which iterates over the SortedSet in reverse order. 

Let’s illustrate how the Reverse() method works:

var reversedSet = _languages.Reverse();
var firstElement = reversedSet.First();
var lastElement = reversedSet.Last();

Assert.IsInstanceOfType(reversedSet, typeof(IEnumerable<string>));
Assert.AreEqual(firstElement, "TypeScript");
Assert.AreEqual(lastElement, "C");

We can prove that we get an IEnumerable<string> object, which iterates over the _languages SortedSet in reverse order. 

How to Check if Two SortedSets Are Equal in C#

In some cases, we may want to compare whether the current SortedSet and another collection contain the same elements. We can use the SetEquals() method to achieve our goal:

var moreLanguages = new SortedSet<string> { "Assembly", "Pascal", "HTML", "CSS", "PHP" };
var languagesCopy = _languages;

Assert.IsFalse(_languages.SetEquals(moreLanguages));
Assert.IsTrue(_languages.SetEquals(languagesCopy));

The moreLanguages object is not equal to the _languages object, so we expect the SetEquals() operation to return false.

Note this method ignores the order of elements and any duplicates in the other collection compared to the current SortedSet

Store Unique Elements From Two SortedSets

Sometimes, we may want to modify a SortedSet to store unique elements between two sets. That’s where the SymmetricExceptWith() method comes into play, as we can use it to accomplish our purpose.

Let’s look at using the SymmetricExceptWith() method:

var moreLanguages = new SortedSet<string> { "Assembly", "Pascal", "HTML", "CSS", "PHP" };

_languages.SymmetricExceptWith(moreLanguages);

Assert.AreEqual(_languages.Count(), 14);

The SymmetricExceptWith() method modifies the _languages SortedSet to make it have unique elements from both itself and moreLanguages SortedSet. Therefore, since both sets have unique values, the modified _languages SortedSet now contains fourteen elements. 

How to Search for an Element in a SortedSet in C#

Besides using the inbuilt Contains() method to check whether a SortedSet contains a specific value, we can use the inbuilt TryGetValue (T equalVal, out T actualVal) technique to achieve the same result. The method takes two parameters, the first being the value to search for and the next being what the search finds or the default value when the search doesn’t yield any results. 

Let’s put this theory into practice:

Assert.IsTrue(_languages.TryGetValue("C#", out _));
Assert.IsTrue(_languages.Contains("C#"));
Assert.IsFalse(_languages.TryGetValue("Assembly", out _));
Assert.IsFalse(_languages.Contains("Assembly"));

Here, _languages does not contain “Assembly” but contains “C#” and uses the discard operator to ignore the TryGetValue() method’s return value.  

Perform a Union Between Two SortedSets

When we want to join two sets, we perform a union operation. For example, when we want to perform a union between two sets, A and B, we copy the elements in set B over into set A. 

Let’s perform a UnionWith() operation between _languages and moreLanguages SortedSet to illustrate this concept:

var moreLanguages = new SortedSet<string> { "Assembly", "Pascal", "HTML", "CSS", "PHP" };

_languages.UnionWith(moreLanguages);
Assert.AreEqual(_languages.Count(), 14);

The UnionWith() method copies the elements in moreLanguages SortedSet into the _languages SortedSet hence, the latter now has fourteen elements instead of nine. 

Benefits of Using a SortedSet in C#

First, SortedSet objects facilitate quick insertion and retrieval operations as they have a constant access time of O(1).

Also, we can use the SortedSet class in applications that do not allow duplicate elements, which helps us eliminate data redundancy.

A SortedSet can quickly check if an element is present in the set without iterating through all elements and returns elements in a specific order

Overall, a SortedSet in C# can significantly aid in managing and manipulating sorted collections of unique elements.

Drawbacks of Using a SortedSet in C#

Using the SortedSet may not be suitable for all situations where we need to store null values or duplicate elements.

Additionally, the performance benefits of using a SortedSet may not be significant in small collections.

In some cases, accessing and manipulating elements by their index may also be necessary rather than simply iterating through them in sorted order. Another collection type, such as a list, may be more appropriate in such cases.

Overall, it is crucial to carefully evaluate whether the restrictions and advantages of using a SortedSet are suitable for a given situation before implementing it in C# code.

SortedSets vs SortedLists

One similarity between a SortedSet and a SortedList in C# is that both data structures maintain their elements in sorted order. However, a SortedSet does not allow duplicate elements, unlike a SortedList. In addition, a SortedSet has faster insertion and removal times than a SortedList, as the latter internally uses an array structure, which requires shifting elements around during insertion and removal.

Conclusion

SortedSet in C# provides a valuable tool for managing and manipulating sorted collections of unique elements. However, it is essential to carefully consider whether its restrictions and advantages make it suitable for a specific situation before implementing it in code. If you enjoyed this article, feel free to check out our article on a similar topic, HashSet in C#.