Click here to Skip to main content

Response to: A question about Casting Generics or "Dynamic Generics"

Revision 10
As requested, I'm answering an edited version of the question is a separate answer. This question is indeed radically different from the original question. :-) All the problem is the misuse of the non-generic method of generic interface. Here is how a fixed version of the code might look (and it will work, of course):
public class DerivedSomething : ISomething<int> {
    public void DoSomething(int item) {
        // Do stuff here, <T> is important for item.
    } //DoSomething
    public bool MustStop {
        // <T> does not matter!
        get { return true; }
    } //MustStop
} //class DerivedSomething

public interface ISomething<T> {
    void DoSomething(T item);
    bool MustStop { get; }
} //ISomething<T>

class Test {
    void TestMethod() {
        DerivedSomething thing = new DerivedSomething();
        if (thing.MustStop)
            Console.WriteLine("Stop!");
        else
            Console.WriteLine("Don't stop!");
    } //TestMethod
} //class Test
I also renamed Something to ISomething to meet (good) Microsoft naming conventions for interfaces. There is certain confusion due to the fact you miss one important step which partially defeats the purpose of generic interfaces. To make the inheritance chain really practical, you need to make is like this: generic interface => generic (possibly abstract) class implementing methods agnostic to generic parameters => set of complete implementation classes — using the idea shown in my previous answer. The term "class" could be replaces with "struct". [EDIT #1] This is amazing: I just made a real "Freudian slip" (http://en.wikipedia.org/wiki/Freudian_slip[^])! In the above paragraph, I wanted to type "(good) Microsoft naming conventions", but typed "goof Microsoft naming conventions"! No, they are really good, not goof; and I'm using them. [END EDIT #1] [EDIT #2] After some thinking, I realized that I can see two more problems in the code above. I explained above, that 1) missing intermediate implementation class implementing just non-generic part of the interface; in this way you would not repeat implementation of those non-generic methods/properties in each of generic type-specific implementation. Two remaining problems are: 2) interfaces are designed to be use during run-time; remove all interfaces from your code — it will work in exact same way, why writing them? you should really use your classed via interfaces; 3) you use implicit interface implementation (via "public"); but explicit interface implementation is much clearer. My fix of the problems (2) and (3) required change in usage but then the use of interfaces is really justified in a practically beneficial way, and lack of code reuse if fixed (the schema you want is useful only when you need to have more than one class like DerivedSomething, but if you try to write more classes you will see that MustStop implementation is not reused); so, the code fixing all together will look like this:
// interfaces:

    public interface IAgnostic {
        bool MustStop { get; }
    } //interface IAgnostic

    public interface ISomething<T> : IAgnostic {
        void DoSomething(T item);
    } //ISomething<T>

//implementations:
 
    abstract class Agnostic : IAgnostic {
        bool IAgnostic.MustStop {
            get { return true; }
        } //MustStop
    } //class Agnostic

    class DerivedSomething : Agnostic, IAgnostic, ISomething<int> {
        void ISomething<int>.DoSomething(int item) {
            // Do stuff here, <T> is important for item.
        } //DoSomething
        //implementation of MustStop is reused
    } //class DerivedSomething
    class DerivedSomethingElse : Agnostic, IAgnostic, ISomething<int> {
        void ISomething<int>.DoSomething(int item) {
            // Do stuff here, <T> is important for item.
        } //DoSomethingElse
        //implementation of MustStop is again reused
    } //class DerivedSomething

// usage:

    class Test {
        void TestMethod() {
            DerivedSomething thing = new DerivedSomething();
            //an extra variable somethigReferenced helps to
            //avoid type case, which would be less safe:
            ISomething<int> somethigReferenced = thing; 
            if (somethigReferenced.MustStop)
                Console.WriteLine("Stop!");
            else
                Console.WriteLine("Don't stop!");
        } //TestMethod
    } //class Test
I fix to the lack of code reuse is done via additional interface IAgnostic that is, agnostic to generic parameters and additional abstract implementation class IAgnostic. The fix is demonstrated by having two different terminal completely implementing classes, DerivedSomething and DerivedSomethingElse. Now it's really right. [END EDIT #2] Now, I gave you the resolution which does not even touch the root topic collectively named "covariance and contravariance". To really understand these issues you really need to learn this topic. In C# practice, it's mostly about new features introduced to C# v.4 in comparison with prior versions. So, try to read and understand this: http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29[^], http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx[^] (you really need to read all parts of it, but it needs some effort to find them; the navigation in this blog really needs improvement!), http://blogs.msdn.com/b/ericlippert/archive/2007/10/26/covariance-and-contravariance-in-c-part-five-interface-variance.aspx[^], http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx[^]. Just to give you the idea how much C# and .NET are lost in covariance/contravariance issues:
//you can do this:
string[] strings = new string[] { "1", "2", };
object[] objectsobjects = strings;

//but you cannot do this, no matter with v.4 or prior version:
System.Collections.Generic.List<string> strList = new System.Collections.Generic.List<string>();
System.Collections.Generic.List<object> objList = strList; //with case or not
The covariance/contravariance issues come into play each time there is a class using instances of other classes when using and used types are derivable. So, the issues are about arrays, generic containers and generic delegates. This goes beyond the casting topics we're discussing here.
—SA
Posted 16-Oct-11 11:28am by Sergey Alexandrovich Kryukov.
Tags: , , , ,


Advertise | Privacy | Mobile
Web01 | 2.8.140421.2 | Last Updated 5 Nov 2009
Copyright © CodeProject, 1999-2014
All Rights Reserved. Terms of Use
Layout: fixed | fluid