tags:

views:

113

answers:

1

I wrote some extensions methods to handle the css class like how you would do it in jQuery. Since I'm using ASP.NET the framework has two extension points i need to handle.

The first one is System.Web.UI.WebControls.WebControl

public static bool HasCssClass(this WebControl control, string className)
public static void AddCssClass(this WebControl control, string className)
public static void RemoveCssClass(this WebControl control, string className)

and the second one is System.Web.UI.IAttributeAccessor

public static bool HasCssClass(this IAttributeAccessor accessor, string className)
public static void AddCssClass(this IAttributeAccessor accessor, string className)
public static void RemoveCssClass(this IAttributeAccessor accessor, string className)

the dilemma here has to do with that the CssClass property of WebControl doesn't seem to reflect accordingly when you access the property through the IAttributeAccessor interface.

i.e.

WebControl c;
c.CssClass = "hello-world";
Debug.Assert(((IAttributeSelector)c).GetAttribute("class") != "hello-world");

I did some digging around with the .NET Reflector and came to the conclusion that the assertion will hold. I can modify the same underlying HTML property class in two distinct ways. Thus my problem, which overload do I use, or rather, which overload does the compiler use?

Now, System.Web.UI.WebControls.WebControl implements System.Web.UI.IAttributeAccessor and this is why I ask the question, which overload will be used? Or is the call ambiguous between the two?

I always imagine that the compiler/run-time calculated the distance to pick one type over the other.

Say TextBox, which derives from WebControl that implements IAttributeAccessor would call the WebControl overload because the distance to WebControl is shorter, strictly speaking.

I found some stuff on MSDN about this, however, it doesn't say what it means to be better than or worse than. So what's going to happen when I call.

TextBox c;
c.AddCssClass("hello-world");

?

EDIT

I ended up changing the IAttributeAccessor to HtmlControl instead, becuase it still covers most cases and would be less confusing for other developers on my team.

IntelliSense actually displayed both as possible overloads, while only one, in fact, is possible.

+3  A: 

You were nearly there - you wanted section 7.4.2.3 instead - "better conversion". It's not a matter of conversion "distance" though. (In the C# 3 spec this is 7.4.3.4 in case you're interested.)

The conversion from TextBox to WebControl is better than the conversion to IAttributeSelector, because:

If an implicit conversion from T1 to T2 exists, and no implicit conversion from T2 to T1 exists, C1 is the better conversion.

There's an implicit conversion from WebControl to IAttributeSelector but not vice versa.

Now that we know which conversion is better, we can apply the section you referenced to determine that the overload with WebControl will be picked. This can easily be demonstrated with a test application of course:

using System;

public interface IFoo {}
public class Bar : IFoo {}
public class Baz : Bar {}

public static class Extensions
{
    public static void Extension(this IFoo foo)
    {
        Console.WriteLine("Extension(IFoo)");
    }

    public static void Extension(this Bar bar)
    {
        Console.WriteLine("Extension(Bar)");
    }
}

public class Test
{
    static void Main()
    {
        Baz b = new Baz();
        b.Extension();
    }
}

This prints "Extension(Bar)" - corresponding to the extension method via WebControl in your real case.

Of course, if you cast to b to IFoo then the overload printing "Extension(IFoo)" will be picked - because the compiler doesn't know that the other overload will be applicable. Remember that overloading is only performed at compile time (leaving aside dynamic in C# 4).

Jon Skeet
I think i got it, but I had to write it down several times before it was clear. T1 is WebControl, T2 is IAttributeAccessor, S is TextBox and has an implicit conversion to both but IAttributeAccessor does not have an implicit conversion to WebControl. T1 is the type. Do you know if there is a formal name for this? And as always I'm amazed over how you find the time to answer all these questions ;) Thanks! But if T1 is T2, but T2 isn't T1, doesn't that imply a distance? When the distance is the same, there's a rule for working out an ambiguity but the distance argument seems true to some extent.
John Leidegren
@John: I suspect you can get quite far using a "distance" analogy and intuition, but I prefer to deal with the terms in the spec - they're easier to reason from, and it's a common frame of reference for communication. I'm sure there will be some corner cases which the "distance" analogy doesn't work with. As for a formal name - a name for what, exactly? Overload resolution, determining the "better" function member, determining the "better" conversion?
Jon Skeet
Agreed, I was just thinking in terms of type theory, if this is a well know way of dealing with overload resolution. Can I rely on the same principe for say outher staically typed programming languages such as Java.
John Leidegren
@John: Each language is going to have its own set of rules, and they may well be subtly different. It becomes more interesting when you get overloads introduced in derived classes etc... See the first example here, for instance: http://www.yoda.arachsys.com/csharp/teasers.html
Jon Skeet
That's wicked... S is int, I don't see it. What makes object the better overload, and this clearly dispoves the distance argument. Anyway, when I read this, If S is T1, C1 is the better conversion. I look at it and think, it implies that no conversion takes place and when you reverse it, If S is T2, C2 is the better conversion, then that implies an ambiguity. There's no mention an ambiguity in 7.4.2.3. so that can't be it, then I look in 7.4.2.2 and conclude that for this to compile at least one argument has to have a better conversion. That is, the boxing is somehow a better conversion.
John Leidegren
@John: In that case it's not a matter of conversions - the overload for `int` isn't even considered, because of the way overloads are found. The compiler stops at the most derived class, and the only *new* signature that introduces is the `object` one.
Jon Skeet
I read the answer later... but why would the compiler do such a thing in the first place? Why is a method from the most derived class higher up the list? What reasoning is behind making such a choice?
John Leidegren
@John: It's for versioning reasons, basically. It's to avoid the "brittle base class" problem, where introducing a new method in the base class changes the meaning of an existing method call. I think that's reasonable *except* in the case where the derived class is also overriding that method.
Jon Skeet
Okay, I see now. I bet I won't run into any troubles caused by this in the near future but it's good to have a handle on things. Thank you.
John Leidegren