.NET, Generics, and Wildcards

1:18 pm programming

Java’s generic system (with all of its type erasure horror) is generally regarded as being bad, but it does have one feature that is very useful: wildcard generic types. The lack of wildcards in .NET trips me up time and time again.

Today, the culprit was Nullable<T>. I want to have a method that will effectively do a ToString() of the Nullable’s value, but return some other string if the Nullable does not contain a value. (I happen to want to make it an extension method, but that’s irrelevant here).

I want to do this:

int foo? = null;
foo.ToStringNullable("No Value"); // returns "No Value"
foo = 42;
foo.ToStringNullable("No Value"); // returns "42";

The code I want to write is this:

public static String ToStringNullable(
  this Nullable<?> nullable,
  String inCaseOfNull) {

  String value;
  if (nullable == null || !nullable.HasValue) {
    value = inCaseOfNull;
  }
  else {
    value = nullable.ToString();
  }
  return value;
}

Note the <?>: this is a Java-ism that does not work in .NET. Java has the wildcard type “?” for generics: it means accept any type. .NET doesn’t have this. Instead, I’d be forced to write:

public static String ToStringNullable<T>(
  this Nullable<T> nullable,
  String inCaseOfNull)
  where T : struct

…which in turn means I’d have to redundantly specify the type of the first parameter, like so:

int foo? = null;
// returns "No Value"
foo.ToStringNullable<int>("No Value");

…which in turn gives me the opportunity to makes mistakes like:

int foo? = null;
foo.ToStringNullable<bool>("No Value");

(although this does generate a compiler error, so it’s not so bad — just tiresome).

(Also note: The “where T : struct” at the end is an additional requirement for the use of Nullable; it’s not important to the argument.)

One would legitimately think that you could use <Object> in place of <T>. However, this is not allowable in .NET. The problem is that some, but not all operations are necessarily typesafe when doing this. Consider:

// This is conceptually sound, but problematic
List<Object> aListOfObjects = new List<String>();

// This won't ever work
aListOfObjects.Add( -1 );

// However, there's nothing conceptually wrong with this
aListOfObjects.Contains( -1 );

This unsolved problem is why I didn’t tag this post as a “Stupid .NET Trick.” The .NET language team made a reasonable decision to forgo wildcards so that they could avoid the erasure mess of Java. It was a tradeoff, and probably a good one overall — but that doesn’t mean that it doesn’t cause problems.

I’ve come up with one way around this problem:

  1. Declare an interface
  2. Give this interface a name like [Type]Typeless. For example, if you had a type Foo<T>, you might name your interface FooTypeless
  3. Let your generic-typed class implement the interface (ex: Foo<T> : FooTypeless)
  4. Create signatures (methods, properties) in the interface for all of the members in the implementing class that happen to not use the generic type parameters. (ToString() is an example, although a bad one since it’s inherited from Object. I’ll provide a better example below.)
  5. Whenever you need to access the non-generic-type members of the class, use the interface instead.

Thus, I would do something like this:

public interface NullableTypeless {
  bool HasValue { get; }
  // Not necessary, but provided for example purposes
  String ToString();
}
public struct Nullable<T> : NullableTypeless {}

Note that NullableTypeless does not have any type-specific members such as <T> Value { get; }.

Then my extension method could be written like so:

public static String ToStringNullable(
  this NullableTypeless nullable,
  String inCaseOfNull ) {

  String value;
  if (nullable == null || !nullable.HasValue) {
    value = inCaseOfNull;
  }
  else {
    value = nullable.ToString();
  }
  return value;
}
// This would work fine:
int foo? = null;
foo.ToStringNullable("No Value");

This works acceptably well in my own code. Unfortunately, since I can’t rewrite .NET, this option isn’t available in the case of Nullable.

Ideally I’d like to see some sort of modifier to methods that says “this method is safe to use if the value supplied to a generic argument is higher up on the inheritance tree than the type that was actually used to create the class”. You could use this on a method-by-method basis. For example, List<T>.Contains(<T>) would be tagged with it (since it doesn’t matter what the argument is; everything has Equals()) but List<T>.Add(<T>) would not (since it can only accept members that are the same or lower down than the List’s inherent type). I don’t think this ever will happen in .NET though.

7 Responses

  1. Craig’s Linked List » Blog Archive » .NET Generics and Type Inference Landmine Says:

    [...] .NET, Generics, and Wildcards [...]

  2. Mark Sowul Says:

    You do know that .NET infers the type from usage, right?

    foo.ToStringNullable(”No Value”);

    would work fine.

  3. Mark Sowul Says:

    By that I mean the generic case.

    given this:

    public static String ToStringNullable(Nullable nullable, String inCaseOfNull)…

    …this works fine
    int? foo = null;

    Console.WriteLine(ToStringNullable(foo, “nothing”));

    works fine.

  4. Mark Sowul Says:

    ugh, i lost my generic args.

    given this:

    public static String ToStringNullable(Nullable nullable, String inCaseOfNull)…

    …this works fine
    int? foo = null;

    Console.WriteLine(ToStringNullable(foo, “nothing”));

  5. Mark Sowul Says:

    still no luck. fine. anyway i see you’ve already written about it.

  6. Craig Says:

    You probably have to HTML-escape your angle brackets (like so: &ltT&gt;); I know I have to when writing posts (since I have HTML turned on).

    You are correct: type inference will in fact work with my ToStringNullable() method, and thus my FooTypless interface is unnecessary here.

    However, FooTypeless is still useful for other cases where type inference isn’t applicable… namely with collections. You still can’t do this:

    List<Foo<Object>> list = new List<Foo<Object>>();
    list.Add(new Foo<String>());
    list.Add(new Foo<int>());

    You have to declare the collection as List<FooTypeless> instead.

    So, the spirit of the post is still correct; I just gave a very bad example. :-P

  7. Mark Sowul Says:

    I agree; indeed, I came here thinking you may have found a better workaround. Type-erasure sucks, but it’s too bad that .NET lacks co- and contravariance.

    However! The latter can pretty much always be worked around with generic methods (further mitigated by type inference and extension methods); the limitations of type-erasure often cannot (or require far uglier workarounds).

Leave a Comment

Your comment

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.