views:

187

answers:

5

I want to implement a collection, whose items need to be tested for emptiness. In case of a reference type, one would test for being null. For value types, one has to implement empty testing, and probably choose a specific value that represents emptyness.

My generic collection of T should be usable for both value and reference type values (meaning that Coll<MyCalss> and Coll<int> should both be possible). But I have to test reference and value types differently.

Wouldn't it be nice to have an interface that implements an IsEmpty() method, to exclude this logic from my generic type? But of course, this IsEmpty() method cannot be a member function: it could not be called on empty objects.

One workaround I found is to have collection items stored as objects, rather then T-s, but it gives me a headache (around boxing and being strongly typed). In good old C++ it was no problem :-)

The code below demonstrates what I'd like to achieve:

using System;
using System.Collections.Generic;

namespace StaticMethodInInterfaceDemo
{
  public interface IEmpty<T>
  {
    static T GetEmpty();  // or static T Empty;

    static bool IsEmpty(T ItemToTest);
  }


  public class Coll<T> where T : IEmpty<T>
  {
    protected T[] Items;

    protected int Count;


    public Coll(int Capacity)
    {
      this.Items = new T[Capacity];
      this.Count = 0;
    }

    public void Remove(T ItemToRemove)
    {
      int Index = Find(ItemToRemove);

      // Problem spot 1: This throws a compiler error: "Cannot convert null to type parameter 'T'
      // because it could be a non-nullable value type. Consider using 'default(T)' instead."
      this.Items[Index] = null;

      // To overcome, I'd like to write this:
      this.Items[Index] = T.Empty;  // or T.GetEmpty(), whatever.

      this.Count--;
    }


    public T[] ToArray()
    {
      T[] ret = new T[this.Count];

      int TargetIndex = 0;
      for(int Index = 0; Index < this.Items.GetLength(0); Index++)
      {
        T Item = this.Items[Index];

        // Problem spot 2: This test is not correct for value types.
        if (Item != null)
          ret[TargetIndex++] = Item;

        // I'd like to do this:
        if (!T.IsEmpty(Item))
          ret[TargetIndex++] = Item;
      }

      return ret;
    }

    protected int Find(T ItemToFind)
    {
      return 1;  // Not implemented in the sample.
    }
  }
}
+3  A: 

You can use the 'default' keyword for this, as in:

this.Items[Index] = default(T);
Eric Minkes
Is kinda funny, because in his comment the compiler already made this suggestion. What's wrong with it?
flq
The problem is the default value might be a valid value and does not determine "emptyness".
tyranid
Correct, Tyranid.
+6  A: 

You can create an extension method for your Interface called IsEmpty. Then you could first test if the 'this' parameter in this extension method is null.

So you could call the IsEmpty method on any reference to a type that is implementing your interface, not regarding if this could be null or not.

Sebastian P.R. Gingter
Two problems with this approach:1.) I did not mention that I have to program against .Net 2.0 (no support for extension methods)2.) In the end, I need the interface to be used in the wehre clause of the class declareation. This way I could not tell the compiler that T supports .IsEmpty, could I?
Extension Methods are .NET 2.0. It's just the newer C# compiler that enables the extension method syntax. You can compile with the new compiler against 2.0 and still use Extension Methods. You could even use LINQ in 2.0 if you'd deploy the System.Core.dll from Mono with your application ;-)And of course this would work out, even when using it in a generic constraint.
Sebastian P.R. Gingter
Well, if you say so, I'll give it a shot :-)Thx.
+3  A: 

There is no such thing as an "empty" int, so supporting int will be tricky unless you store a bit-map of which are defined - but if you simply use a collection of int? (i.e. Nullable<int>) it is done for you. No extra work, and no boxing:

List<int?> list1 = ...; // data
List<string> list2 = ...; // data

Console.WriteLine(list1[3]; == null); // actually maps to `item.HasValue`
Console.WriteLine(list2[3]; == null); // reference test

To get an int from an int?, any of:

int i = (int)value; // throws exception if empty
int i = i.Value; // throws exception if empty
int i = i.GetValueOrDefault(); // returns 0 if empty

And when you don't need to be able to test for empty, just a List<int> (no ?). No extra code. No special collection class. And it works for most anything.

Marc Gravell
I like it.Still, I need to create my collection (a HashTable). How can I specify that type T must be a Nullable type?"where T : Nullable" is not available(and of course I don't need it for an int, but for any struct)
You can't. There is no single constraint that covers ref-type and `Nullable<T>`.
Marc Gravell
A: 

Instead of using an array in your class to represent the collection, use a stack or list. Then you don't have empty indexes, they simply aren't there if they where removed.

Gary Willoughby
I need the empty representation :-)
A: 

How about just sending in two functions in the constructor?

public Coll(Func<T> createEmpty, Func<T, bool> isEmpty)
{
    this.createEmpty = createEmpty;
    this.isEmpty = isEmpty;
}

You could then use those functions later:

if (!isEmpty(item))

Items[index] = createEmpty();
Svish