On Apr 7, 2008, at 09:28, Yossi Kreinin wrote:

I would argue that more people would intuitively think that IList<B> inherits from IList<A> if B inherits from A than that it does not (and that generic methods are instead required).

I think that the only reasonable test for "is Y a subclass of X?" is "can I substitute Y objects whenever X objects are used?". Y="collections of derived class objects" fails this test when X="collections of base class objects".


I agree with the test of "can I substitute Y objects whenever X objects are used?", but I don't follow -- in your example -- under which circumstances I would be unable to substitute IList<B> wherever IList<A> is expected.

(All examples are in untested C#.)

If covariance is supported, then I can do the following:

class A
{
  property bool IsAvailable { get; set; }

  property string Name { get; }

  void Save(Stream stream);
}

class B : A
{
  property int NumAttempts { set; }

  void UpdateFromWeb();
}

void PlayWithAList(IList<A> list)
{
  Stream stream = new MemoryStream();
  foreach (A a in list)
  {
    if (a.Name == "test1")
    {
      a.IsAvailable = !a.IsAvailable;
    }
    a.Save();
  }
}

void main()
{
  IList<B> bList = GetListFromWebService();
  PlayWithAList(bList);
}

Where can I not substitute A with B in the method "PlayWithAList"? Without covariance, I can write the method like this:

void PlayWithAList<T>(IList<T> list) where T : A
{
  // body is the same
}

As mentioned in the previous mail, this works just fine *as long as I have control of the method definition*. If the method definition is in a framework and the designer didn't make the method generic, then you're out of luck and have to either convert or cast the argument, which is a shame.

Granted, with containers, you leave yourself open to the possibility of adding an element to the list which will cause a runtime error. Suppose we also have class C:

class C : A { }

void PlayWithAList(IList<A> list)
{
  list.Add(new C());
}

If I'd passed in IList<B> as the parameter to this method, it would cause a runtime exception even though the compiler had evaluated statically that it was legal.

Interestingly, C# does support covariance in one limited case: when calling delegates.

Assume the following delegate definition:

delegate void PlayWithA(A a);

Then let's redefine the method call "PlayWithAList" to be:

void PlayWithAList(IList<A> list, PlayWithA func)
{
  Stream stream = new MemoryStream();
  foreach (A a in list)
  {
    if (a.Name == "test1")
    {
      a.IsAvailable = !a.IsAvailable;
    }
    func(a);
    a.Save();
  }
}

Since C# supports covariance, you can call "PlayWithAList" like this:

void main()
{
  IList<B> bList = GetListFromWebService();
  PlayWithAList(
    bList,
    delegate(B b)
    {
b.UpdateFromWeb(); // Method is only defined in B, but covariance works here!
    }
  );
}

Cheers
Marco

--
Marco Von Ballmoos
http://earthli.com - Home of the earthli WebCore; PHP web sites made simple.


Reply via email to