18

I'm specifically thinking about a collection that fulfills the contract of a set, but I think the question can apply to any kind. Are there collections in the .NET framework that prevent null entries? The specific behavior I want is this:

var set = new HashSet<object>();
bool added = set.Add(null);
Console.WriteLine(added); // prints "False"

This isn't the behavior of the built-in HashSet<T>. Are there any collections that do have this (or similar) behavior, or am I better off rolling my own? If the latter, what's the best way to go about it? Should I inherit directly from HashSet<T> or just wrap it?

EDIT: To be clear, this is just idle wondering. Mostly because I can't think of any reason I'd ever want to allow null into a set of objects. I don't have any particular need for this.

Sean Devlin
  • 1,662
  • 12
  • 17
  • 6
    Don't fix this, it just hides bugs in your code. Use an explicit if() statement before you call the Add() method. Use an extension method if you really, really have to. – Hans Passant Feb 02 '10 at 18:19
  • 1
    @nobugz +1. This is good advice. The reason to do this is definitely **not** to patch up broken code. – Sean Devlin Feb 02 '10 at 19:16

10 Answers10

13

There isn't a built-in class just like HashSet<T> except for this single behavior.

If you need this, I'd recommend rolling your own. I do not recommend subclassing HashSet<T>, however. None of the methods (like Add, which you explicitly want to change) are virtual, as it wasn't really designed with subclassing in mind. This will cause strange usage behavior, since you'd be hiding the inherited methods.

Just encapsulate a HashSet<T>, and expose the members you need. The only real "code" you'd have to add is a single null check on the Add method - otherwise, just pass through all of the methods to the encapsulated instance.

If you want this to be a generic class, you'll need to add an extra constraint to only work with classes, since you want to have a null check:

public class ValueSet<T> : ICollection<T>, IEnumerable<T>, ICollection
    where T : class
{
     private HashSet<T> hashSet = new HashSet<T>();
     // ... Implement all members as needed...
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • 1
    Make sure to implement `ICollection` if you implement `ICollection`. The latter does *not* implement the first, and it has made some code rather annoying where I want to allow usage of both `HashTable` and `HashSet`... – Sam Harwell Feb 02 '10 at 18:31
  • @280Z28: Good point. I always thought it strange that ICollection doesn't implement ICollection. Thanks for adding that. – Reed Copsey Feb 02 '10 at 19:05
  • This seems like the best advice for someone looking to go this route. Accepted. – Sean Devlin Feb 02 '10 at 19:14
8

What about writing an extension method for HashSet. It may be the easiest thing to do.

public static class HashSetExtensions
{
    public static bool AddNonNull<T>(this HashSet<T> set, T item)
        where T : class
    {
        if (item == null)
            return false;

        return set.Add(item);
    }
}

You can then do this:

HashSet<object> test = new HashSet<object>();
test.AddNonNull(null); //Will return false;
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
AGoodDisplayName
  • 5,543
  • 7
  • 35
  • 50
  • I almost always choose a solution to avoid extension methods, so I wouldn't use this. That said, I made some updates so it's usable should someone go that way. :) – Sam Harwell Feb 02 '10 at 18:43
  • I was about to do the same, but not only did you beat me by 9 years you also thought to add a boolean return value - which as it turns out makes what I want to do even easier and cleaner :-) – Simon_Weaver Feb 12 '19 at 07:53
3

As you tagged this question with generics I suppose you are looking for a generic collection. Such collection does not exist in the .NET framework simply because value types cannot be null, so I guess you should have to roll your own by adding a constraint restriction on the generic type it accepts.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
2

A System.Collections.Generics.Dictionary<TKey, TValue> doesn't let you add null for TKey (throws an exception). You'd have to ignore TValues then in your scenario if you intend to use it that way, and the functionality would be similar to the Hashset<T>, except the fancy set operations of course.

Surely a bit clumsy but maybe it's a liable workaround for you in the meantime before you can come up with your own fancy collection type.

herzmeister
  • 11,101
  • 2
  • 41
  • 51
1

I am not aware of any .NET collection that does that, as based on the type passed a null is actually a valid entry, thus, the add is successful.

I would most likely start off from the System.Collections.ObjectModel.Collection class for rolling your own.

Mitchel Sellers
  • 62,228
  • 14
  • 110
  • 173
  • Collection wouldn't be appropriate to implement a HashSet - it doesn't have the same storage semantics, and performance characteristics. – Reed Copsey Feb 02 '10 at 18:22
1

Even if you manage to prevent ADDING null entries, you will not be able to prevent HAVING null entries inside the set - at least not by means of your Set class.

Take any Set<T> where T is a nullable type. After you have added some non-null entries, you could take any of them and set it to null. Since that command has nothing to do with the Set class, it cannot be detected nor prevented by the Set class.

Kjara
  • 2,504
  • 15
  • 42
  • Good point. You could however work around this by only having a Get and Set method that takes an index. – rollsch Jan 22 '17 at 06:38
0

Two options:

Edit: Sorry I missed he needs a set and not a collection. At least the preferred item I listed is still valid. :)

Preferred:

In the method that adds items to the collection, throw an ArgumentNullException if they are called with an invalid value.

Not preferred:

Derive from Collection<T> and override InsertItem and SetItem to throw an ArgumentNullException if the value is null.

Sam Harwell
  • 97,721
  • 20
  • 209
  • 280
0

In any case, you could use Code Contracts in .NET 4.0 to prevent null values to be added into a collection. This way, you'll also get a compile-time checking whether the code is correct (using static analysis provided by Code Contracts):

public class MyClass {
  private List<Something> nonNullList;

  [ContractInvariantMethod]
  void NonNullEntries() {
    Contract.Invariant(Contract.ForAll(nonNullList, el => el != null));
  }

  public void Add(Something el) {
    Contract.Requires(el != null);
  }

}

The method marked with ContractInvariantMethod specifies condition that should hold at all times (there are no null elements in the list). I believe that the static analysis cannot reason about this (because of ForAll), but I may be wrong. However, it certainly can check whether you're calling Add method with correct (non-null) arguments.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
0

As others have answered, there isn't a Set class that does not allow nulls. Having said that, I'll share a simple solution I used in a few situations. In my case, I was designing methods that accepted HashSet arguments because I needed to ensure I'm getting unique objects. I didn't want a possible null in the set and I didn't want to check for null every time I was enumerating the items and performing operations on them. My solution was to simply call remove null in case there was a null.

public void MyMethod(HashSet<SomeType> set)
{
    // remove null item in case it's there
    set.Remove(null);

    foreach (var item in set)
    {
        // safe to assume item is not null
        item.DoSomething();
    }
}
tomosius
  • 1,369
  • 12
  • 18
-1

You're gonna have to roll your own. If you want Add to return a bool (whether the item was non-null and therefore added or the inverse), you'll have to start from scratch. I might recommend inheriting from List and throwing ArgumentNullExceptions. I mean if you really don't want nulls added, exceptions might be the best way and will certainly be easier.

hackerhasid
  • 11,699
  • 10
  • 42
  • 60
  • I don't see why you'd have to start from scratch. `HashSet.Add` already returns `bool`. It seems like could just return `false` in case of `null` and otherwise defer to `HashSet`. EDIT: Since the method is not virtual, you would need to defer to `HashSet` explicitly for everything, which would be a pain. But still better than starting from scratch. – Sean Devlin Feb 02 '10 at 18:31
  • -1: The ***only*** reason you can *ever* derive from `List` is to provide a constructor that takes a special kind of initializer or to provide new operations. You absolutely cannot override its behavior on Add. See my answer here: http://stackoverflow.com/questions/1192022/how-to-create-a-custom-collection-in-net-2-0 – Sam Harwell Feb 02 '10 at 18:35