It’s like Deja-vu


Many months ago I wrote a blog discussing an issue we were having with the Implement-Interface smart tag concerning what we should do when we cannot perform an action that the user has requested.  Well (like most unresolved issues) the topic has reared it’s ugly head and we are faced with a couple more confusing situations.

The first problem is most evident due a recent change in the BCL concerning our core collections classes.  Basically at the root of everything are the following two classes:

      public interface IEnumerable

      {

            IEnumerator GetEnumerator();

      }

 

      public interface IEnumerable<T> : IEnumerable

      {

            new IEnumerator<T> GetEnumerator();

      }

(the recent change was to make IEnumerable<T> implement IEnumerable).  So why is this an issue?  Well imagine you have typed the following:

      public class MyCollection<T> : IEnumerable<T>

      {

      }

 

and you then ask us to implement the interface implicitly.  Well, as it turns out we can’t implement that interface implicitly.  If we did you’d end up with

      public class MyCollection<T> : IEnumerable<T>

      {

            public IEnumerator<T> GetEnumerator()

            {

                  throw new Exception(“The method or operation is not implemented.”);

            }

 

            public IEnumerator GetEnumerator()

            {

                  throw new Exception(“The method or operation is not implemented.”);

            }

      }

 

which is illegal C# code as you are not allowed to have two methods with the same name as same parameter types.  So how would a user normally resolve this where two interfaces conflict with eachother?  In C# you would use explicit interface implementation and you would write the second method as:

           IEnumerator IEnumerable.GetEnumerator()

            {

                  throw new Exception(“The method or operation is not implemented.”);

            }

 

So what do we do about it.  Right now we’re falling back to implementing one of the methods explicitly so that we don’t produce broken code.  In this case we’re leaning toward that being more important than strictly following the letter of what was stated earlier in the smart tag.  Other options that we could have done would be to produce the broken code (not very nice since you have to fix it up), pop up a message telling you that we did that (with all of the associated burden of more UI annoying you), or disabling the option to implement the interface implicitly (with all the associated confusion from the user as to why it was no longer an option).

 

The second problem is a little more subtle, and it’s definitely not clear what we should be doing.  Say you have the following code:

      public interface IFoo

      {

            void DoIt();

      }

 

      public class Base

      {

            public void DoIt() { }

      }

 

      public class Derived : Base, IFoo

      {

      }

 

and you ask to implicitly implement IFoo.  Currently our behavior is to dump the method in the ‘Derived’ class.  Part of me thinks that this is broken behavior.  ‘Derived’ already implements ‘IFoo’ through Base::DoIt, so it doesn’t need any extra implementation.  On the other hand, sometimes people actually want ‘Derived’ to reimplement the IFoo interface by hiding the member in the base class (by using the ‘new’ keyword).  Consider the example above, but this time you have a fully fleshed out IFoo interface with many methods and ‘Base’ implements just one of them.  A user might be very suprised after implementing all of IFoo’s methods that the DoIt method is actually calling into the ‘Base’ class instead of the class they thought they were implementing it on.

 

A common pattern that occurs in my code is to have a deeply nested object hierarchy that mimics a deeply nested interface hierarchy.  So you have something like:

      public interface IEnumerable { /*…*/ }

      public interface IIterable : IEnumerable { /*…*/ }

      public interface ICollection : IIterable { /*…*/ }

      public interface IMap : ICollection { /*…*/ }

      public interface ISortedMap : IMap { /*…*/ }

      public interface IList : ISortedMap { /*…*/ }

 

      public class ArrayEnumerable : IEnumerable { /*…*/ }

      public class ArrayIterable : ArrayEnumerable, IIterable { /*…*/ }

      public class ArrayCollection : ArrayIterable, ICollection { /*…*/ }

      public class ArrayMap : ArrayCollection, IMap { /*…*/ }

      public class ArraySortedMap : ArrayMap, ISortedMap { /*…*/ }

      public class ArrayList : ArraySortedMap, IList { /*…*/ }

 

So, for me if i’m asking to implement “IList” on “ArrayList” i definitely do not want stubs for every interface in that hierarchy generated.  They’ve already been implemented so why would i want to implement them again?  All that happens is I end up with massive code spew that i then have to clean up afterwards.

 

So I’m changing the behavior slightly (and adding some configurability) and i wanted to get your thoughts on it.  Here are the rules now:

  1. If you’re implementing an interface member ‘implicitly’
    1. If your type contains an implicit member that matches the interface member then we don’t generate it
    2. If the option “check super types for matching members” is set and one of your super types contains an implicit  member that matches the interface member then we don’t generate it
    3. If one of your super types contains an implicit member that conflicts with the interface member then we generate it with the “new” keyword to indicate that hiding is occurring
    4. If one of the methods in your type conflicts with the interface then we explicitly implement i
  2. If you’re implementing an interface member ‘explicitly’
    1. If your type contains an explicit member that matches the interface member then we don’t generate it
    2. If the option “check super types for matching members” is set and one of your super types contains an explicit member that matches the interface member then we don’t generate it

By default we will be set to check super types when you’re implementing implicitly and to not check super types when you’re implementing explicitly.

 

To me this fits best with the model of “implement interface simply does the minimal work necessary to get your code actually implementing the interfaces it declares.”  However, if you feel that you’re a person who wants this feature to stub out the entire interface and then you prefer to remove the possible unnecessary stubs, then there are options to all you to go back to that behavior.

How do you feel about that?  Are we being totally boneheaded here?  Is there an easy way to do the right thing and have everyone understand what’s going on?


Comments (59)

  1. Gabe Halsmer says:

    I have a definite opinion on interface implimitations. Why can’t a method with a more specific return type match an implementation? This would solve your first problem.

    For example…

    interface ISimple

    object GetObject();

    class Simple : ISimple

    string GetObject();

    Simple’s method GetObject should satisfy the ISimple contract. However, the current version of C# doesn’t agree and gives a compile error saying you haven’t implemented a method GetObject() that returns an object. But string is an object!

    If C# would accept this, then it solves your 1st problem. IEnumerator<T> is a sub-type of IEnumerator. So you don’t have to implement GetEnumerator twice. One method returning IEnumerator<T> satisfies the contract for IEnumerable.

    So I guess the question becomes, why doesn’t C# accept a method whose return type is a sub-type. Let’s consider this example…

    class A

    virtual int GetValue() { … }

    class B : A

    override int GetValue() { return 214; }

    Now B has implemented its own version of GetValue. And its dramtically limited the scope of what values can be returned from that method. Its domain of possible values is acutally just one value, 214.

    So obviously a sub-class can re-implement a method and limit the domain of possible values that method will return. But then why can’t it change the return type of the method to reflect this? I’m not sure if uint is a sub-type of int, but let’s say that it is (logically POSITIVE_INTEGER is a sub-type of INTEGER). Why can’t B implement the method with a unit since it knows that it will never return a negative number.

    class B : A

    override uint GetValue() { return 214; }

    This should not be considered a new method. Its the same method because uint is an int. If I had a collection of A’s and called GetValue on each, I would expect an int back. And if some of those A’s happened to B’s (because B is a sub-type of A) and they returned a uint, well that’s still fine because uint IS an int. I have not violated A’s contract in anyway.

    So my question is, what does C# not allow this. Will it change in C# 2.0?

  2. Gabe: Because the runtime doesn’t support covariant return types. We won’t have support for taht in C# 2.0 (even though i want it really badly). We also won’t have co/contravariant generics either.

    However, even with covariant return types the problem still exists (albeit not in the form above). All you need is:

    interface IFoo {

    int Foo();

    }

    interface IFoo2 : IFoo {

    new string Foo();

    }

    class C : IFoo2 //<– can’t implement both Foo’s

  3. Sean Chase says:

    I found myself re-reading 1.3 and 2.2 a couple of times, but I like "minimal work necessary" approach personally. Thanks for asking and caring.

    Also, I thought Covariance was supported…or is that only for delegate return values?

  4. Sean: Yup. Only for delegates.

  5. DrPizza says:

    lame.

    I don’t understand why covariant return types and contravariant argument types weren’t supported from the outset. Do they make something somewhere much more complicated? If so, it’s not clear to me how.

  6. DrPizza says:

    "This should not be considered a new method. Its the same method because uint is an int."

    It certainly shouldn’t be.

    uint and int are both narrower than long, so if the base class declared the return type as long, methods returning int and uint should both be reasonable overrides (as they’re narrowing the return type, which is safe).

    If the base class declared the argument type as short, a method taking int (or long) as an argument should also be a reasonable override (as they’re both wider than the argument type, which is safe).

    The domain of int and uint both fit into long, but they don’t fit into each other. So to claim that uint is an int seems rather silly to me. It might have the same size, and some numbers may have the same representation, but they’re not identical.

  7. Ron says:

    Cyrus,

    I think your approach is the appropriate action (generally), but I’m left wondering about implementing interfaces with partially overlapping methods. For example:

    public interface IFoo

    {

    ..void Initialize(object value);

    ..object GetValue();

    }

    public interface IBar : IFoo

    {

    ..new int GetValue();

    }

    Would implicit interface implementation be like so:

    public class MyValue : IBar

    {

    ..public int GetValue()

    ..{

    ….throw new Exception(…);

    ..}

    ..public void Initialize(object value)

    ..{

    ….throw new Exception(…);

    ..}

    ..public object IFoo.GetValue()

    ..{

    ….throw new Exception(…);

    ..}

    }

    If so, is there a potential point of confusion here? Does it matter if a subset of members is explicitly implemented due to conflicts?

  8. Samuel Jack says:

    A picky point about consistency:

    In your examples of the code generated by "implement interface" you show that the methods throw Exception("The method or operation …"). Is that actually what you generate. If so, shouldn’t it be throw NotImplementedException("…")?

  9. Dr.Pizza:

    "lame.

    I don’t understand why covariant return types and contravariant argument types weren’t supported from the outset. Do they make something somewhere much more complicated? If so, it’s not clear to me how. "

    ++

    What’s worse is that we don’t have any sort of variance system for generics. So there is no way to do something like.

    IList<Object+> = new ArrayList<string>();

    Oh well, hopefully in the future.

  10. Samuel Jack: "NotImplementedException" is not available in the compact .net framework 1.0.

    We had to make a decision about the best possible exception versus the fact that the code we’d be generating wouldn’t compile on one of our larger platforms.

    Because tehy’re just .snippet files you can (and we’d like you to) edit them to be appropriate for your needs.

    We’re still trying to figure out if there’s a way to support both scenarios OOB simply.

    (good catch btw).

  11. Note, the "Object+" syntax is just a throwaway syntax for teh example. I got a good chance to talk to one of hte greators of the java1.5 type system (who also agrees taht not changing the VM was a bad idea) and about how we could add variance into our type system safely.

    The only problem is how to do it in a way that does freak out every C# user.

    So I’m hoping we do generics, give people a change to adjust to them, then enhance them based on community demand.

  12. I know this is a tangent from the real issue, but…

    Not only are the internal representations of uint and int different, but you really can’t say that every valid uint value could be represented by an int.

    Consider:

    class B : A

    override uint GetValue() { return 2147483648; }

    It returns a valid uint, but not a valid int.

  13. DrPizza says:

    How do covariant return types/contravariant argument types have any influence on the type system?

    It’s not clear what extra magic they might need. Particularly not in something like C# where overrides are explicitly declared as such.

  14. DrPizza says:

    "What’s worse is that we don’t have any sort of variance system for generics. So there is no way to do something like.

    IList<Object+> = new ArrayList<string>(); "

    I don’t see why there should be such a capability. A list-of-string is not a list-of-object. List-of-object has a method equivalent to put(Object o). List-of-string does not. List-of-string should not be _in_any_way_ convertible to list-of-object short of creating a new list-of-object populated from the list-of-string.

    The problem is that the list interface has the element type as both an argument (for ‘put’ actions) and a return type (for ‘get’ actions). That means no subtyping for you. What you’ve written is fine for the ‘get’ portion (making the return type more restrictive is fine), but not for the ‘put’ portion; the reverse (IList<string> = new ArrayList<object>()) is fine for the ‘put’ portion (making the argument less restrictive is fine), but not for the get portion.

    Allowing *co*variant arguments and *contra*variant return types is frankly wrong; I think Eiffel permits it and inserts some runtime checks to stop things from breaking too badly.

  15. DrPizza: It matters because the runtime needs to determine which method your method is overriding when you mark it overridden. When you override a method the method that you overrode is not explicitly encoded into your dll. Indeed in cannot be for the following reason:

    you have the following classes:

    //Assembly A, v1

    class A {

    public virtual void Foo() { … }

    }

    class B : A {

    }

    //Assembly B, v1

    class C : B {

    public override void Foo() { … }

    }

    You compile this but then Assembly A gets changed to have

    class B : A {

    public override void Foo() { … }

    }

    Now when the runtime loads your method it needs to know that your Foo method is now override B’s Foo as opposed to A’Foo. (otherwise things like "base<dot>" will no longer work correctly). So the runtime resolves overloads. So C# cannot added covariant return types without runtime support.

    —-

    ""IList<Object+> = new ArrayList<string>(); "

    I don’t see why there should be such a capability"

    What that says is "i have a list of somethings that are objects", not "i have a list of objects". In that case you can get things out of the list, but not put things into them. When i take something out of the list i know it at least an object (well, you always know that in C#, but i could have said something like ValueType+ and you’d know it was at least a value type). But you can’t be sure when you put something in that it will be ok because that thing won’t necessary be the type that the list was declared with. If it helps think of it with this inheritance hierarchy.

    ………………….IList<object+>

    ……………………./…………

    ……………………/…………..

    ……IList<string+>…………IList<object>

    ………………….

    …………………..

    …………….IList<string>

    As you can see. IList<string> is a IList<string+> is a IList<object+>, but IList<string> is not an IList<object>

    However, this gives IList<string> and IList<object> a common ancestor IList<object+>.

    So you can think of IList<T+> having all the methods of IList<T> except for any methods that take T as an argument. Similarly you can think of IList<T-> as having all the methods of IList<T> except methods that return T. (note, the +/- syntax was just made up for the sake of example. I’m not sure what syntax i prefer).

    This is nice if you have something like a PrintCollection method that can take any sort of collection and print it. Since i don’t need to mutate it i can take anything. If i claim that i take IList<Object> then someone can’t pass me an IList<String>.

    "Allowing *co*variant arguments and *contra*variant return types is frankly wrong"

    Of course it is. Who said that we would allow that? I was talking about variance across generics.

  16. DrPizza: This is nice because it takes the onus of writing a Base interface with no mutators and a base interface with no accessors off of the library writers hand. Less chance for error, and more flexibility for the user of C# and the BCL than the current generics implementation.

  17. DrPizza says:

    "So the runtime resolves overloads. So C# cannot added covariant return types without runtime support. "

    Sure, the runtime resolves the function, but surely it only matches on argument types, which for covariant return types don’t change? Contravariant arguments can be implemented in code relatively cleanly so are a little less important.

    "What that says is "i have a list of somethings that are objects", not "i have a list of objects"."

    The difference being?

    A list of "somethings that are objects" can have added to it *somethings that are objects*. A new Object() is something that’s an object, and so can be added to a list of somethings that are objects, but it can’t be added to a list of somethings that are strings, because it’s not a string.

    "In that case you can get things out of the list, but not put things into them."

    So no inheritance and no conversion should be allowed. Public inheritance says "is-a" and if you do not do what your parent says you do, you’re not-a subtype of your parent.

    "When i take something out of the list i know it at least an object (well, you always know that in C#, but i could have said something like ValueType+ and you’d know it was at least a value type). But you can’t be sure when you put something in that it will be ok because that thing won’t necessary be the type that the list was declared with. "

    Which is precisely why it shouldn’t be convertible, and why there isn’t an inheritance hierarchy.

    "If it helps think of it with this inheritance hierarchy. "

    It helps me see that there’s no legitimate inheritance here.

    "So you can think of IList<T+> having all the methods of IList<T> except for any methods that take T as an argument."

    Why would I think of it as that? That’s not what it *says* it is. It *says* it’s a frigging IList. But it’s *not*, because it doesn’t do the things that an IList can do.

    "This is nice if you have something like a PrintCollection method that can take any sort of collection and print it. Since i don’t need to mutate it i can take anything. If i claim that i take IList<Object> then someone can’t pass me an IList<String>. "

    Surely the correct solution (the one which doesn’t require making a broken hierarchy where interfaces and objects claim to be able to do things that they can’t and claim to be things that they aren’t) is to make PrintCollection itself parametrized? Something as if (in faux MC++, because I don’t know exactly how the new MC++ works, or the enumerator interface off-hand):

    template<typename T> void PrintCollection(IList<T>^ container)

    {

    for(IList<T>::Enumerator^ enum(container->getEnumerator()); enum->hasNext(); enum->moveNext())

    {

    std::cout << enum->item() << std::endl;

    }

    }

    That way we don’t need to pretend that we have IList-that-aren’t-ILists, and our print method can print anything.

    "Of course it is. Who said that we would allow that? I was talking about variance across generics. "

    You’re talking about creating an inheritance hierarchy in spite of covariant arguments and contravariant return types.

    "This is nice because it takes the onus of writing a Base interface with no mutators and a base interface with no accessors off of the library writers hand."

    Why would they need to?

    Why not just declare methods that take IList<T> as actually taking IList<T>?

  18. Jim Argeropoulos says:

    I know what covariant is, but I have never seen contravariant before. Googling for it sends me into the math world of vectors, which is probably not closely related.

    Could you enlighten me?

    Thanks

  19. Gabe Halsmer says:

    My example of uint/int was bad because as some people have pointed out, uint IS NOT a sub-type of int. Logically, PositiveInteger would be a subset of Integer, but uint and int are not true domain classes since they have some physical limitations on their size. So uint cannot inherit from int.

    This concept of covariance and contravariance only applies to types that inherit from each other.

    And I take it that covariance referrers to argument variance, where a super-class defines a method with a specific argument like MemoryStream but a sub-class overrides it and redefines the argument to something more general like Stream.

    And it sounds like contravariance applies to return types, where the super-class returns something general (Stream) but a sub-class overrides it and returns something more specific (MemoryStream).

  20. Gabe Halsmer says:

    @DrPizza

    "Allowing *co*variant arguments and *contra*variant return types is frankly wrong"

    Why is it wrong?

  21. DrPizza says:

    "Why is it wrong? "

    ‘cos you’re no longer overriding, because you’re no longer accepting compatible arguments.

  22. DrPizza says:

    "I know what covariant is, but I have never seen contravariant before. Googling for it sends me into the math world of vectors, which is probably not closely related.

    Could you enlighten me?

    "

    The opposite of covariant, really.

    To permit covariant return types for overrides in subtypes means that the return type of the subtype’s method can vary in the same sense as the inheritance. That is to say, more derived subtypes’ methods can have more derived return types. A common example of this is if you have a "clone" type method:

    struct Base

    {

    ….virtual Base* clone() { /* … */ }

    };

    struct Derived : Base

    {

    ….virtual Derived* clone() { /* … */ }

    };

    struct MoreDerived : Base

    {

    ….virtual MoreDerived* clone() { /* … */ }

    };

    Those derived clone methods are all overrides of the Base class’s, because Derived*s are convertible to Base*s–a method returning a Derived* fulfils the requirements of a method returning a Base*.

    A language permitting contravariant arguments would allow arguments of methods of subtypes to be *less* derived than the arguments of their bases. C++ doesn’t permit it, but if it did, it might allow something like this:

    struct Base

    {

    };

    struct Derived : Base

    {

    };

    struct MoreDerived : Base

    {

    };

    struct CovariantBase

    {

    ….virtual void foo(MostDerived* arg) { /* … */ }

    };

    struct CovariantDerived

    {

    ….virtual void foo(Derived* arg) { /* … */ }

    };

    struct CovariantMostDerived

    {

    ….virtual void foo(Base* arg) { /* … */ }

    };

    Again, these should all act as overrides. A function which accepts a Base* will certainly also accept a MostDerived*. So it’s safe to permit. C++ doesn’t treat them as overrides mostly because it’s not so useful as covariant return types, as it’s easy to implement "by hand":

    struct CovariantDerived

    {

    ….virtual void foo(MostDerived* arg) { return foo(static_cast<Derived*>(arg)); } // this one overrides

    ….virtual void foo(Derived* arg) { /* … */ } // and this one is the faux contravariant version

    };

  23. Dr.Pizza: Say i am a class that has a list of something in it (say strings). I want to allow you to pass me some information and in the process of doing so i am going to add these strings to a collection that you provide.

    Now, the easiest way to do that would be to force you to provide an ICollection<string>. But why is that necessary? All i’m doing is providing you with the data and in your case an IList<object> would suffice for storing those strings. However, it’s not possible to pass an IList<object> to a method needing an ICollection<string>. Of course, i could relax my method and have it take an ICollecction<object>, but then if you actually had an IList<string> then you still wouldn’t be able to pass that in to me. The problem is that all i need you to pass me is a general container that can hold anything, i.e. an ICollection<string->.

    Your example with managed C++ still won’t work.

    If I have a method that looks like:

    AddMyStuffToYourCollection<T>(ICollection<T> c) {

    ….c.AddAll(myStuff); //<– not allowed. AddAll needs to make sure that myStuff is a container of T or things derived from T. We cannot guarantee that.

    }

    ——-

    "Sure, the runtime resolves the function, but surely it only matches on argument types, which for covariant return types don’t change? Contravariant arguments can be implemented in code relatively cleanly so are a little less important. "

    No, the runtime matches on the entire signature. So, as i stated, C# would need the runtime to change to support this. Again, this is something i would like to see happen. But it cannot work without coordinated development here.

    ———

    "What that says is "i have a list of somethings that are objects", not "i have a list of objects"."

    "The difference being? "

    The difference being that in the first list i know that the list contains objects, but i don’t know exactly what the type is. If you’re familiar with existential types then it’s equiv to:

    IList<object+> === There Exists X such that IList<X>

    IList<string+> === There exists X such that IList<X> and X isa string.

    etc.

    IList<string-> === There exists X such that IList<X> and string isa X.

    etc.

    ———

    " A new Object() is something that’s an object, and so can be added to a list of somethings that are objects, but it can’t be added to a list of somethings that are strings, because it’s not a string. "

    No it can’t. If you have a list of things that are objects, that could be an actual list of objects, a list of strings, a list of ints, etc. You most certainly cannot add an object to that list.

    ———

    "So no inheritance and no conversion should be allowed. Public inheritance says "is-a" and if you do not do what your parent says you do, you’re not-a subtype of your parent. "

    You do do what your parent allows. As long as you match the type restrictions placed on it.

    If i am an IList<string> i *am* an ICollection<string>. Does that mean I can Add(new Object()) ? No. Because the type singatures don’t match. Similarly if i am an ICollection<string> i *am* an ICollection<object+>, but that doesn’t let Add(new Object()), because the type signatures don’t match. In this case you will not find a type T for which Add(T) type checks.

    However! If i know that a type is IList<string+> and some bit of other information tells me that it’s also iList<string->, then we have the fixed point case where now I can know for sure that it’s IList<string> and i can treat it as such.

    —–

    "It helps me see that there’s no legitimate inheritance here."

    how so? Once again you’re being too terse. What is invalid about the inheritance hierarchy shown? The type system fits, and it allows for more flexibility when dealing with generics so that it can more easily fit in with your programming model.

    ——-

    "Why would I think of it as that? That’s not what it *says* it is. It *says* it’s a frigging IList. But it’s *not*, because it doesn’t do the things that an IList can do. "

    You’re right. I shouldn’t have tried to simplify it in that manner. It is an IList and it can do everything that an IList can do. And, as long as you satisfy the type restrictions you willbe able to do that. If you can’t, then the compiler will error and tell you right away (just like with regular generics).

    ———-

    "That way we don’t need to pretend that we have IList-that-aren’t-ILists, and our print method can print anything. "

    We don’t need to pretend anything. These things are what they are. They just use existential types. And as usual with those types, you must supply a proof (in the form of constraints) to show what the set of types are that are valid in that situation. This is far more fflexible, because as i said it no longer requires the user to prove that they can have objects added to them when all i want them to do is prove that they can give objects back to me. Normal generics provide a large onus on the consumer to make the types exactly match what is specified *even* if it it isn’t necessary for their object to be exactly of that type. Variant generics allow for completely legal (no exceptions at runtime) substitutions without losing any of the expressiveness of the normal ones.

    ——–

    ""Of course it is. Who said that we would allow that? I was talking about variance across generics. "

    You’re talking about creating an inheritance hierarchy in spite of covariant arguments and contravariant return types. "

    No, the inheritance hierarchy already exists. You just can’t access it today. This is highly aggravating when dealing with situations where you are restricted from using a perfectly resonable type because of type restrictions in place right now.

    ———-

    ""This is nice because it takes the onus of writing a Base interface with no mutators and a base interface with no accessors off of the library writers hand."

    Why would they need to? "

    Because they don’t need the functionality of a full list, and by forcing users to provide items that match that full functionality they put a large burden on the library consumer that has a perfectly valid alternate item to use.

    ———–

    "Why not just declare methods that take IList<T> as actually taking IList<T>? "

    Because it often won’t work and because it makes library consumption rather difficult.

    ————

    Note: (non-managed) C++ gets away with this with structural subtyping where the template is instantiated in place and proven to meet the needs of the caller and callee. (yaay for a complete functional language in the compiler). So i can say i need an IList<T> and i just add my strings to that list. When you call me with an ICollection<object> the method is instantiated in the compiler and it verifies that in fact an ICollection<object> is perfectly acceptable as a container for strings. the compiler in the C++ case does the job of supplying the proof for the existential types and allows this to continue. However, in .net/C# generic methods aren’t templates. They aren’t instantiated and examined by every one who calls into them. There are issues of reflection to deal with which means that at run time we must know everything about the method and must be able to determine if it’s acceptable to be called based on the arguments being passed in.

  24. "public class Derived : IFoo" should be "public class Derived : Base, IFoo" right?

  25. Mattias: Yup. Fixed. Thanks for the catch.

  26. DrPizza says:

    "Now, the easiest way to do that would be to force you to provide an ICollection<string>. But why is that necessary? All i’m doing is providing you with the data and in your case an IList<object> would suffice for storing those strings. However, it’s not possible to pass an IList<object> to a method needing an ICollection<string>. Of course, i could relax my method and have it take an ICollecction<object>, but then if you actually had an IList<string> then you still wouldn’t be able to pass that in to me. The problem is that all i need you to pass me is a general container that can hold anything, i.e. an ICollection<string->. "

    So *don’t specify* what type I pass you. Just specify that you want a container of T, and let *me* specify what T is when I pass you the container.

    "Your example with managed C++ still won’t work. "

    There’s no reason it can’t work, and it would work without breaking subtyping, unlike your proposal.

    "If I have a method that looks like:

    AddMyStuffToYourCollection<T>(ICollection<T> c) {

    ….c.AddAll(myStuff); //<– not allowed. AddAll needs to make sure that myStuff is a container of T or things derived from T. We cannot guarantee that.

    } "

    I don’t see what you mean by "not allowed". Its allowableness will depend on what AddAll is. AddAll ought to take a ICollection<U> where U is constrained by deriving or being T.

    I can do this in C++, so why can’t it be done in .NET?

    "No, the runtime matches on the entire signature."

    Why? Does it permit something interesting as a result of this?

    "The difference being that in the first list i know that the list contains objects, but i don’t know exactly what the type is."

    But you don’t know exactly what the type is in the second either. Just because you’ve got an IList<object> doesn’t mean that they’re all actually objects.

    "If you’re familiar with existential types then it’s equiv to:

    IList<object+> === There Exists X such that IList<X>

    IList<string+> === There exists X such that IList<X> and X isa string.

    etc. "

    But there exists no such X for any type other than string.

    IList<object+> === There Exists X such that IList<X>

    IList<string+> === There exists X such that IList<X> and X isa string.

    etc.

    Our IList looks something like:

    interface IList<T>

    {

    T get();

    void put(T t);

    }

    class List<T> : IList<T>

    {

    T get();

    void put(T t);

    }

    And let’s have a little hierarchy:

    struct B {}

    struct D : B {}

    So you want to be able to have

    IList<B+> b1 = new List<D>;

    D1 is-a B, so that should be a good enough match for B+.

    B is also a B, so we can also go:

    IList<B+> b2 = new List<B>;

    But this is now no good.

    I can do this:

    b2.add(new B());

    but I can’t do this:

    b1.add(new B());

    So why are you making them subtype?

    "IList<string-> === There exists X such that IList<X> and string isa X. "

    The same happens here.

    "Similarly if i am an ICollection<string> i *am* an ICollection<object+>, but that doesn’t let Add(new Object()), because the type signatures don’t match."

    But they do match, because object is-a object, so is compatible with object+.

    "You’re right. I shouldn’t have tried to simplify it in that manner. It is an IList and it can do everything that an IList can do. And, as long as you satisfy the type restrictions you willbe able to do that. If you can’t, then the compiler will error and tell you right away (just like with regular generics). "

    Those type restrictions make it not an IList. It no longer does what an IList can do. It’s not substitutable.

    "We don’t need to pretend anything. These things are what they are. They just use existential types. And as usual with those types, you must supply a proof (in the form of constraints) to show what the set of types are that are valid in that situation. This is far more fflexible, because as i said it no longer requires the user to prove that they can have objects added to them when all i want them to do is prove that they can give objects back to me."

    So write a function template and they don’t have to. And they don’t have to have ILists-that-aren’t-ILists. An IList that returns something it can’t insert isn’t an IList any more, because the contract of IList says you can do that.

    "Normal generics provide a large onus on the consumer to make the types exactly match what is specified *even* if it it isn’t necessary for their object to be exactly of that type."

    Rubbish.

    "No, the inheritance hierarchy already exists."

    Except that they’re not subtypes so don’t belong in an inheritance hierarchy.

    "Because they don’t need the functionality of a full list, and by forcing users to provide items that match that full functionality they put a large burden on the library consumer that has a perfectly valid alternate item to use. "

    But users aren’t forced to do any such thing.

    "Because it often won’t work and because it makes library consumption rather difficult. "

    It works in C++ and it doesn’t make library consumption difficult.

  27. Dr.Pizza: "So *don’t specify* what type I pass you. Just specify that you want a container of T, and let *me* specify what T is when I pass you the container. "

    This won’t work. Try to write it. if i make my method parameterized on T then i will not be able to add strings to it.

  28. DrPizza: "I don’t see what you mean by "not allowed". Its allowableness will depend on what AddAll is. AddAll ought to take a ICollection<U> where U is constrained by deriving or being T. "

    Exactly! So how do i add a string to that? We just said that it’s an ICollection<U> where U is derived from T. But neither T nor U make the gurantee that this collection can hold strings.

  29. DrPizza: ""No, the runtime matches on the entire signature."

    Why? Does it permit something interesting as a result of this? "

    No, it doesn’t. But that’s how it’s implemented. And, as i’ve said over and over again, we cannot do covariant return types without this support. That’s all i’m saying. If the runtime relaxes this restriction in teh future then we will be able to do it. But, that requires both them and us to do work.

  30. DrPizza:

    "

    IList<B+> b1 = new List<D>;

    D1 is-a B, so that should be a good enough match for B+.

    B is also a B, so we can also go:

    IList<B+> b2 = new List<B>;

    But this is now no good.

    I can do this:

    b2.add(new B());

    but I can’t do this:

    b1.add(new B()); "

    No, you can do neither. All you have done is declare that there is some type (that is at least a B) that these lists were instantiated with. You have not stated that that type actually *is* B. so you will not be allowed to add anything to these lists.

  31. DrPizza:

    "Similarly if i am an ICollection<string> i *am* an ICollection<object+>, but that doesn’t let Add(new Object()), because the type signatures don’t match."

    But they do match, because object is-a object, so is compatible with object+. "

    No. ICollection<object+> says "i am a collection of some type, where that type is at least an object, but not necessarily object itself.

    i.e. i’m a collection of ints (ints are object), i’m a collection of strings (string are object), i’m a collection of objects (objects are objects). But i don’t know which one i am. So adding an object to me is not supported because you haven’t supplied proof that i was actually instantiated as IList<object>

  32. DrPizza:

    ""We don’t need to pretend anything. These things are what they are. They just use existential types. And as usual with those types, you must supply a proof (in the form of constraints) to show what the set of types are that are valid in that situation. This is far more fflexible, because as i said it no longer requires the user to prove that they can have objects added to them when all i want them to do is prove that they can give objects back to me."

    So write a function template and they don’t have to. And they don’t have to have ILists-that-aren’t-ILists. An IList that returns something it can’t insert isn’t an IList any more, because the contract of IList says you can do that. "

    a) We don’t have templates in C#

    b) Where does IList ever state that it can insert something it returns?

    IList states that it can add something that is known to match the type the list was instantiated with. If you don’t know what taht type is then you can’t add something.

    If i instantiate a list as:

    IList<string-> then i know that this was instantiated as either IList<string> or IList<object> and its’ safe to add either of those types to this list.

  33. DrPizza:

    ""Normal generics provide a large onus on the consumer to make the types exactly match what is specified *even* if it it isn’t necessary for their object to be exactly of that type."

    Rubbish. "

    Explain. I’ve already given examples of where this arises. You have not shown how to do it otherwise with generics. "Rubbish" isn’t an argument.

    ——-

    "No, the inheritance hierarchy already exists."

    Except that they’re not subtypes so don’t belong in an inheritance hierarchy. "

    Yes, they are subtypes and there are many proofs showing them to be such.

    —-

    "Because they don’t need the functionality of a full list, and by forcing users to provide items that match that full functionality they put a large burden on the library consumer that has a perfectly valid alternate item to use. "

    But users aren’t forced to do any such thing. "

    Yes they are. There is no way for me (as a user) to do much of what i’ve already mentioned. If someone takes an ICollection<string> with the intent of only placing strings in it I cannot provide any ICollection<object> even though that is a perfectly fine container for the elements this person is providing.

    —–

    "Because it often won’t work and because it makes library consumption rather difficult. "

    It works in C++ and it doesn’t make library consumption difficult. "

    C++ has templates. I already said taht. These are not templates and it’s incorrect to think of them as such.

    Generics in .net are weaker than templates (by design). allowing for this type-safe inheritance to be exposed through teh language is one way of making of for some (But obviously not all) of the limitations. Without a full functional language at our disposal at compile time we can only prove things like this by adding them to the type system instead of realying on C++ to check things like structural subtyping and matching (concepts which have no analogue in C#).

  34. DrPizza says:

    "This won’t work. Try to write it. if i make my method parameterized on T then i will not be able to add strings to it. "

    You seem to jump between what the system _does_ let you do and what you _want_ it to let you do, but you don’t appear to afford me the same privilege.

    It is possible to construct a system where this works in the way I describe; such systems even exist.

    "Exactly! So how do i add a string to that? We just said that it’s an ICollection<U> where U is derived from T. But neither T nor U make the gurantee that this collection can hold strings. "

    Likewise.

    "No, it doesn’t. But that’s how it’s implemented. And, as i’ve said over and over again, we cannot do covariant return types without this support. That’s all i’m saying. If the runtime relaxes this restriction in teh future then we will be able to do it. But, that requires both them and us to do work. "

    Why did they make it match signatures in such a way, then? Is there some benefit to doing it? Does the runtime permit overloading by return type or something? Hrm, I suppose it can’t, or else you could use a similar approach to the contravariant argument workaround.

    "b) Where does IList ever state that it can insert something it returns? "

    T this[int index] { get; set; }

    "IList states that it can add something that is known to match the type the list was instantiated with."

    It does more than that. It also says that the things it returns match the type the list was instantiated with, because we have:

    T this[int index] { get; set; }

    Even if I don’t know what T *is* I do know that it’s both the return type and argument type of the indexer. I don’t care what it is–they necessarily match.

    "If you don’t know what taht type is then you can’t add something. "

    You don’t have to know what it is; just that it’s been returned by IList. And I can in any case find out the type, by asking the runtime.

    "Explain. I’ve already given examples of where this arises."

    Not on this page you haven’t.

    "Yes, they are subtypes and there are many proofs showing them to be such. "

    How can they be shown to be such when these "subtypes" have different interfaces (losing, as they do, one method or the other due to contravariant return types and covariant argument types)?

    "Yes they are. There is no way for me (as a user) to do much of what i’ve already mentioned. If someone takes an ICollection<string> with the intent of only placing strings in it I cannot provide any ICollection<object> even though that is a perfectly fine container for the elements this person is providing. "

    Why.

    Are.

    They.

    Taking.

    An.

    ICollection<string>.

    If.

    That.

    Is.

    Not.

    What.

    They.

    Really.

    Want.

    I mean, seriously. What is your point? If I only want to place strings in a container but don’t care if the container is homogeneous **WHY WOULD I SPECIFY THAT THE CONTAINER ***MUST*** CONTAIN STRINGS**? It’s like complaining that a method that takes a string argument can’t take arbitrary objects as its argument. No, of course it can’t *so don’t do that*. Make it take an object.

    "Generics in .net are weaker than templates (by design)."

    More "different" than "weaker" I would think. They’re closer in capabilities to C’s qsort (with its void*s and function pointers, allowing the _same_ function to compare _different_ types selected at _runtime_) with some added typechecking (and complete ignorance of LSP) than templates. Different set of trade-offs. Generics, of course, allow simple separate compilation, which templates lack.

    "Without a full functional language at our disposal at compile time we can only prove things like this by adding them to the type system instead of realying on C++ to check things like structural subtyping and matching (concepts which have no analogue in C#). "

    Well that’s hardly my fault.

  35. DrPizza: "Why.

    Are.

    They.

    Taking.

    An.

    ICollection<string>.

    If.

    That.

    Is.

    Not.

    What.

    They.

    Really.

    Want. "

    Because there is nothing else for them to take…

    They want a collection to put strings into. What are the options? ICollection<string> or ICollection<object>. Both put restrictions on the user as to what htey can pass in. The former prevents them from passing in an ICollection<object>, the latter an ICollection<string>. However, both would be acceptable for the task.

  36. DrPizza: ""This won’t work. Try to write it. if i make my method parameterized on T then i will not be able to add strings to it. "

    You seem to jump between what the system _does_ let you do and what you _want_ it to let you do, but you don’t appear to afford me the same privilege.

    It is possible to construct a system where this works in the way I describe; such systems even exist. "

    How do you construct such a system with the current generics in C#?

  37. DrPizza: ""b) Where does IList ever state that it can insert something it returns? "

    T this[int index] { get; set; }

    "IList states that it can add something that is known to match the type the list was instantiated with."

    It does more than that. It also says that the things it returns match the type the list was instantiated with, because we have:

    T this[int index] { get; set; } "

    Yes, and that still holds in the variant case.

    However, if instantiate an IList<T> as IList<object+> then i have:

    object+ this[int index] { get; set; }

    then what we have in an IList instantiated such that a proof has been provided that it will always return an object from it’s indexer’s getter, but no such proof has been given about what the indexer’s setter can take. This is a fundamental property of existential types.

    Now, for users who don’t want the burden of proving that their list can accept objects, then this is a perfect scenario to use variance to get around that issue. Likewise for users (commonly library writers) who only want generalized containers to store things into, then variance also allows this.

    Without variance the library writer and library consumer must agree on the exact instantiated type *Even* though all that gets them is the ability to call methods that they don’t even care about.

    variance loosens that restriction on them and allows people to use a wider set of types which might then be more useful to the task at hand.

  38. DrPizza: ""Explain. I’ve already given examples of where this arises."

    Not on this page you haven’t. "

    Yes i have. I’ve given two examples. One from a production standpoint, one from a consumption standpoint. You’ve said they aren’t necessary but haven’t shown code that will do the equivlent. Youv’e givn examples of how to do it in C++ with templates, but those capabilities are not available to us with generics.

  39. DrPizza: "I mean, seriously. What is your point? If I only want to place strings in a container but don’t care if the container is homogeneous **WHY WOULD I SPECIFY THAT THE CONTAINER ***MUST*** CONTAIN STRINGS**? It’s like complaining that a method that takes a string argument can’t take arbitrary objects as its argument. No, of course it can’t *so don’t do that*. Make it take an object. "

    Exactly.

    Now, say you’ve written it is:

    SomeMethod(ICollection<object> collection).

    now the user cannot pass in one of their ICollection<string>’s *even though* all i am going to do is add strings into it.

    Once again, how would you go about doing this? I need to put strings into a container you pass me. Why should i restrict you to just ICollection<object>’s or ICollection<string>’s when either woudl suffice?

  40. DrPizza: ""Without a full functional language at our disposal at compile time we can only prove things like this by adding them to the type system instead of realying on C++ to check things like structural subtyping and matching (concepts which have no analogue in C#). "

    Well that’s hardly my fault. "

    Who said anything about fault? You’ve been stating that we go about this disparaity by using the structral matching that C++ uses in templates. However, we cannot use them as they are purely a compile time mechanism.

    We are using .net generics here and as such these variance ideas would simply be exposing more of the typing system that *already* exists but is currently hidden from you. Regardless of how you feel about this, if T : U then IList<T+> : IList<U+> . That’s true no matter what (whereas IList<T> : IList<U> is definitely not true). However, in the current implementation of generics we do expose this and so we cause pain for anyone who is writing code that really only needs the generics to be co/contravariant.

    As it turns out, we’ve run into a similar problem when designing a class hierarchy for our symbol table. When using C++’s templates we are able to simulate covariant generics (since C++ just does structural matching and see’s that it’s ok since we don’t call any setters). However, when we try to reimplement this in C# generics we are stopped (Both by the lack of covariant return types *and* the lack of variant generics). So, a disgn pattern that was very intuitive and implementable with templates is no longer doable with generics.

    However relaxing the language (And runtime) to support covariant return types and variance would allow the same things to be done taht we do in C++ without any loss of expressibility or type safety.

  41. DrPizza says:

    "then what we have in an IList instantiated such that a proof has been provided that it will always return an object from it’s indexer’s getter, but no such proof has been given about what the indexer’s setter can take."

    They take the same type. It says so right there. It says that what the getter gets the setter can set. That’s why they, you know, use the same type.

    Do you know what type the getter is actually getting? No (well, not withot asking). But that doesn’t matter; it’s the same type as the setter is expecting. object+.

  42. DrPizza says:

    "Who said anything about fault? You’ve been stating that we go about this disparaity by using the structral matching that C++ uses in templates. However, we cannot use them as they are purely a compile time mechanism. "

    But that wasn’t my decision, so why are you making me stick with it? I’m still not convinced that it was a good decision. Sure, separate compilation is nice. But is it really *that* nice? It’s genuinely not clear to me that it is.

    And this assumes that a template model with separate compilation is impossible, and I’m not sure that that’s actually true.

  43. DrPizza says:

    "Yes i have. I’ve given two examples. One from a production standpoint, one from a consumption standpoint. You’ve said they aren’t necessary but haven’t shown code that will do the equivlent. Youv’e givn examples of how to do it in C++ with templates, but those capabilities are not available to us with generics. "

    And nor are the things you’ve talked about, so I don’t get why what I’ve talked about is "invalid".

  44. DrPizza: "They take the same type. It says so right there. It says that what the getter gets the setter can set. That’s why they, you know, use the same type.

    Do you know what type the getter is actually getting? No (well, not withot asking). But that doesn’t matter; it’s the same type as the setter is expecting. object+. "

    Yes, and object+ is sayign… i’m some type, and that type is at least object, but i don’t know any more about it. That type could be int, it could be string, it could be bool. As I said, you haven’t narrowed down the type to a single type, you still jsut have the existential type.

    If you need the ability to do:

    foo[4] = foo[3]

    then you need a list where you can confirm that the types are indeed the same. T+ does not make that confirmation for you. It only makes 1/2 of the argument. You need to know T- as well in order to get the fixed point.

  45. DrPizza: ""Yes i have. I’ve given two examples. One from a production standpoint, one from a consumption standpoint. You’ve said they aren’t necessary but haven’t shown code that will do the equivlent. Youv’e givn examples of how to do it in C++ with templates, but those capabilities are not available to us with generics. "

    And nor are the things you’ve talked about, so I don’t get why what I’ve talked about is "invalid". "

    You’ve stated that the inheritance system presented is invalid and breaks the "isa" relation between types and supertypes. This is an invalid statement.

    and the capabilities I’m discussin (variant generics) *are* available with generics, they are just not exposed.

  46. DrPizza: ""Who said anything about fault? You’ve been stating that we go about this disparaity by using the structral matching that C++ uses in templates. However, we cannot use them as they are purely a compile time mechanism. "

    But that wasn’t my decision, so why are you making me stick with it?"

    No one is making you stick with it. You can use whatever language you want. However, C# does not have templates and probably will not be getting them. We instead went with generics.

    —-

    "I’m still not convinced that it was a good decision. Sure, separate compilation is nice. But is it really *that* nice? It’s genuinely not clear to me that it is. "

    I’m not convinced that it was a good decision either. However, that decision cannot be undone at this point. So what we are discussing is changes to the language to enable similar functinoality to what has been lost in a safe way.

    ————-

    "And this assumes that a template model with separate compilation is impossible, and I’m not sure that that’s actually true. "

    Sorry, i should not have implied it was impossible.

    But again, even if we were to move to a template based model (which allowed structural subtyping and would enable what’s being discussed here), it would not change anything about the validity of the model currently being presented.

  47. DrPizza: I don’t think this discussion medium is well suited to conversing about this topic. I woudl appreciate it if you would continue it with me through the "contact" link. Through that we can discuss your concerns and things like proofs of the type system, existential types, recursive types, structural subtyping. IIt would also be interesting to talk about templates and the issues involved when you want to base your type system on them. I could then post a blog summarizing and explaining what we talked about.

    Thanks!

  48. DrPizza says:

    "Yes, and object+ is sayign… i’m some type, and that type is at least object, but i don’t know any more about it. That type could be int, it could be string, it could be bool. As I said, you haven’t narrowed down the type to a single type, you still jsut have the existential type. "

    But you don’t have to narrow it down. The return type and the argument type of the indexer are the same. It’s already narrowed down for you.

    "If you need the ability to do:

    foo[4] = foo[3]

    then you need a list where you can confirm that the types are indeed the same. T+ does not make that confirmation for you."

    Yes it does.

    The return type and the argument type of the indexer are by definition the same type. That’s what the interface specification says.

    "It only makes 1/2 of the argument. You need to know T- as well in order to get the fixed point. "

    It makes the whole argument. They’re both of type object+.

  49. DrPizza: "It makes the whole argument. They’re both of type object+."

    I’m not sure how to interpret your statement. It’s too terse. As i mentioned before, we can discuss this through the contact link. I think that would be best since it does not appear we are making any headway here.

    As it stands, you are correct. both are of type ‘object+’, which is exactly why you can’t call the setter.

    You haven’t supplied enough information for the existential type to know how the type was actually instantiated as. The type could have been instantiated as IList<int> or any other type (since all types derive from object). Because you haven’t supplied enough information (why i said "only 1/2 of the argument") the type system cannot verify that you can call the setter safely and will prevent you from doing so. However, if you supply enough information to limit (prove) what the type was actually instantiated as then it will be safe to call with an argument of that type.

  50. DrPizza says:

    "As it stands, you are correct. both are of type ‘object+’, which is exactly why you can’t call the setter. "

    But the setter says that I *can* call it with type object+. So why are you claiming I can’t?

    "You haven’t supplied enough information for the existential type to know how the type was actually instantiated as."

    But I don’t need to specify any more information. The indexer says that the return type and the argument type are one and the same. Whatever proving needs to be made has already been done. I don’t need to know _how_the_type_was_actually_instantiated_, because that doesn’t change the interface. All I need to know is that you can set what you get. And you can, because the interface tells you so.

  51. DrPizza says:

    "(the recent change was to make IEnumerable<T> implement IEnumerable)."

    Even though it doesn’t?

  52. Duncan says:

    Very late to the party here – interesting discussion above in which I think you guys need to start over. You both appear to be arguing about 10 unrelated points in a mixed fashion. But largely I think DrPizza is closer to my thinking here. Nonetheless I got a bit lost off after a while!

    Anyway, getting back to the original point.

    The only real answer IMHO is to explicitly implement both methods. This will ensure the code works and that it is clear what has happened – the important stuff. If the user then chooses to make one of them implicit that’s a simple change.

    Of course I have no idea why you’d want to write

    [code]public interface IEnumerable

    {

    IEnumerator GetEnumerator();

    }

    public interface IEnumerable<T> : IEnumerable

    {

    new IEnumerator<T> GetEnumerator();

    }[/code]

    Surely that is not a correct thing to do? Surely what you really meant was:

    [code]public interface IEnumerable<T>

    {

    IEnumerator<T> GetEnumerator();

    }

    public interface IEnumerable : IEnumerable<object>

    {

    }[/code]

    was it not?

    On the second point, I would expect:

    "’Derived’ already implements ‘IFoo’ through Base::DoIt, so it doesn’t need any extra implementation."

    but if you arn’t happy with that then your only real alternative is to implement both methods explicitly:

    [code]

    public class Derived: Base, IFoo

    {

    public void IFoo.DoIt() {throw new NotImplementedException(); }

    public void Base.DoIt() { base.DoIt(); }

    }[/code]

    And allow the user to pick the one they want implicit by simply deleteing a few characters.

    Finally,

    IList<Object+> = new ArrayList<string>();

    that strikes me as just plain wrong. But alas I can’t put my finger on exactly why.

    Object a = new String(); is fine, and we know that we must cast to do:

    String b = (String)a;

    so it would seem that

    IList<object> a = new ArrayList<string>(); would also be fine.

    However I don’t feel it is. I think that comes from the fact that I can derive no valid interpretation for

    (IList<object>)new ArrayList<string>()

    – is there one?

  53. "We had to make a decision about the best possible exception versus the fact that the code we’d be generating wouldn’t compile on one of our larger platforms.

    Because tehy’re just .snippet files you can (and we’d like you to) edit them to be appropriate for your needs. "

    This seems like an excellent thing for your proposed TweakC# utility to set.

    In fact even if it was just a list of files that opened in notepad it would be a bonus to have a utility that lists all the studio snippet files and code templates (and the ability to reset them to default). I currently go through and edit them manually if I want to tweak them to my liking.

    I always hoped the ASP.Net guys would something similar with the intellisense for Web Custom Controls with autogeneration of the intellisense Xml file (though at least there are some third party utils for this) still easy editing of the c# templates would make my day 😉

  54. DrPizza: Please contact me through the links at the top of the page. This format is not conducive to having a good discussion about the type theory issues being raised here.

  55. Duncan: "IList<object> a = new ArrayList<string>(); " has many problems associated with it.

    For example, IList<T> has a method "void Add(T t);" this would imply that i could do:

    a.Add(new object());

    but that’s not allowed because this object was instantiated as an ArrayList of strings and can only hold strings in it.

    one option would be to automatically create a wrapper IList implementation that allwed the add but would do a runtime typecheck to verify that it was the right type. However, we felt that users would hate this. You have a collection that declares it can take objects of a certain type and now it’s throwing exceptions?? Generics were about bringing type safe parametric polymorphism to .net (With a nice added perf bonus as well), and we’d be removing that. Even if it was explicit (i.e. you had to cast to IList<object>) we still got feedback that it would be confusing and unintuitive.

    So we started looking into how to expose an actual typesafe way to accomplish what a user was probably trying to do.

    i.e., if we look at your example we can guess at a few things. First, the user probably was never going to put an object into the list (if they did then then shouldn’t have declared it as an ArrayList<string>), so why were they assigning into an IList<object>? Well, presumably at some point they were going to get elements out of the list and only needed to think of them as objects (maybe because it was only going to access the .Equals method or something). maybe they have a debugging method that prints out any IList<…> no matter what the contents, and all they need to call it .ToString on all the elements. So, in essense they’re saying with that code snippet "i want a list that i can jsut read from, i don’t care about modifying it". The problem is that that’s not something you can express in C# today. However, it is something that existensial types can express (and parametric polymorphism is a subset of that). So if we expose existensial types then you’re allowed a richer type system in which you can say "this is a list of some thing that derives from object, i just don’t know (or care) what that type is". And, we can do this with:

    a) no breaking of type safety

    b) no perf hit

    c) certainly no runtime ClassCastExceptions

    All it is is making more of hte type system available for you to play around with. If you don’t like it you can avoid it and always instantiate your generics with an exact type. If you do like it and think it’s helpful for your problem at hand then you can use variance to get around certain limitations of today.

  56. DrPizza: ""(the recent change was to make IEnumerable<T> implement IEnumerable)."

    Even though it doesn’t? "

    I agree taht it doesn’t. It was jsut used as an example of the complexity of "Implement Interface."

    Any example where you have multiple interfaces with the same method name and arguments would have sufficed.

    I’m sorry i brought up that example in this discussion as it would have been more interesting as a discussion on it own. This post however it concerning "Implement Interface" and how to deal with inferring a users’ intention from teh code they’ve written. Personally, i think inference is terribly difficult, and any rules you come up with run the risk of:

    a) not hitting all scenarios

    b) being too complex for the user to understand

    The alternative is to give the user the flexibility to decide the behavior. However, that can lead to complicated UI and interfering with a code focussed developer who jsut wants the tools to help him and not get in the way.

  57. DrPizza says:

    "Any example where you have multiple interfaces with the same method name and arguments would have sufficed. "

    My preference would be to do the explicit implementation thing.

    However, for the sake of symmetry, I would implement both explicitly, rather than having one implicit, one not.

    For something like the IEnumerable you might reasonably argue that for example because the <T> version is "closer" then it should be implicit and the "more distant" version should be explicit. But that doesn’t solve the problem for class Foo : IBar, IBaz {} where IBar and IBaz overlap; there’s no obvious candidate for implicit implementation.

    For the case where a base class already provides a match, perhaps provide an explicit implementation and make it call the method in question in its superclass.

  58. DrPizza says:

    "DrPizza: Please contact me through the links at the top of the page. This format is not conducive to having a good discussion about the type theory issues being raised here. "

    Can you not plumb in a forum-style backend?

    That’d be much better than these blog comments.