5

I have a basic filter class that stores a string parameter name and a generic T value. The filter has a method Write(writer As IWriter) which will write the contents of the filter to an HTML page. The Write method has two overloads, one which takes two strings and which takes a string and an object. This lets me auto-quote strings.

The problem is, when I call writer.Write(ParameterName, Value) and T is a string, it calls the overload for string/object, not string/string! If I call the Write method on the writer directly, it works as expected.

Here's an SSCE in C#. I tested this in both VB and C# and found the same problem

void Main() {
    FooWriter writer = new FooWriter();

    Filter<string> f = new Filter<string>() {ParameterName = "param", Value = "value"};

    f.Write(writer);                        //Outputs wrote w/ object
    writer.Write(f.ParameterName, f.Value); //Outputs wrote w/ string
}

class FooWriter {
    public void Write(string name, object value) {
        Console.WriteLine("wrote w/ object");
    }

    public void Write(string name, string value) {
        Console.WriteLine("wrote w/ string");
    }
}

class Filter<T> {
    public string ParameterName {get; set;}
    public T Value {get; set;}

    public void Write(FooWriter writer) {
        writer.Write(ParameterName, Value);
    }
}
just.another.programmer
  • 8,579
  • 8
  • 51
  • 90

1 Answers1

8

The problem is, when I call writer.Write(ParameterName, Value) and T is a string, it calls the overload for string/object, not string/string!

Yes, this is expected - or rather, it's behaving as specified. The compiler chooses the overload based on the compile-time types of the arguments, usually. There's no implicit conversion from T to string, so the only applicable candidate for the invocation of writer.Write(ParameterName, Value) is the Write(string, object) overload.

If you want it to perform overload resolution at execution time, you need to use dynamic typing. For example:

public void Write(FooWriter writer) {
    // Force overload resolution at execution-time, with the execution-time type of
    // Value.
    dynamic d = Value;
    writer.Write(ParameterName, d);
}

Note that this could still behave unexpectedly - if Value is null, then it would be equivalent to calling writer.Write(ParameterName, null) which would use the "better" function member - here writer.Write(string, string) - even if the type of T is object!

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    Why is the compile time type of `T` object, not string? I thought the compiler "magically replaces" all the generics in my program with their concrete equivalents when I compile. – just.another.programmer Oct 20 '14 at 16:37
  • 2
    @just.another.programmer: The compile-time type of `T` is just `T`... but there's an implicit conversion of `T` to `object`, but not from `T` to `string`. `Filter` is only compiled *once* - you need to update your understanding of how generics works, basically. – Jon Skeet Oct 20 '14 at 16:39
  • 1
    In the case of `writer.Write(f.ParameterName, f.Value)`, the compiler knows `T` is a string. That has something to do with the declaration `Filter f` I assume? Any good reading material on what the compiler is actually doing in both cases? – just.another.programmer Oct 20 '14 at 16:43
  • 1
    @just.another.programmer: Yes, because the type of `f` is `Filter`. Compare that with the point at which the compiler compiles `Filter` - it converts that into IL *once*, even though `Filter` can be used in multiple places with different types for `T`. As for the best reading material... I'd thoroughly recommend the C# specification. I don't know whether C# in Depth (my own book) would help on this one - I'd like to hope so, but I couldn't say for sure. – Jon Skeet Oct 20 '14 at 16:45
  • 1
    Do you know how to force execution time time overload resolution? Using `Dim d = Value` (the rough VB equivalent of dynamic) doesn't work. – just.another.programmer Oct 20 '14 at 16:53
  • 1
    @just.another.programmer: That's not really the equivalent of `dynamic`, as far as I'm aware. I suspect you'd need to mess with options (strict/explicit) but I'm not a VB expert. – Jon Skeet Oct 20 '14 at 17:01
  • 1
    @just.another.programmer: [VB.Net equivalent for C# 'dynamic' with Option Strict On](http://stackoverflow.com/questions/2889974/vb-net-equivalent-for-c-sharp-dynamic-with-option-strict-on). – Victor Zakharov Oct 20 '14 at 17:04