`

c# - interface backward compatibility

    博客分类:
  • C#
c# 
阅读更多

It is quite common that you might want to change the public interface. while as a lib developer, you should be familiar with some facts/information/lore/knowledgest that you should be aware of;

 

as we all know that the typical operations that we may do on an interface may falls into one/combination of the four categories.

 

  • add a new member
  • remove an existing member
  • rename an existing member (you can think this is a special case of adding a new interface whilst removing an old one)
  • changing the signature / changing the order of the members

 

some of them are safe, or relative safe? let's examine them in turn.

 

 

NOTE: in all the examination, we suppose that lib maintainer has control over both the interface and the impl to the interfaces. otherwise, we know it is a definite deal breaker to change but just a little bit of interface.

 

 

Add a new member

 

Suppose we have two project, one is called InterfaceLib, which represents /embodies a lib, which is maintained by a lib developer; and the other is called InterfaceApp, which represents  an application that uses the InterfaceLib;

 

to test the compatibilty, we will build two version of the same lib, and we will build the InterfaceApp against the first version of InterfaceLib, but we will force to load the second version of the InterfaceLib at runtime; 

 

So, suppose the first version of the IInterfaceLib is like this: 

 

  public interface IInterfaceLib
  {
    object Property1 { get; }

  }
  public class InterfaceLibImpl : IInterfaceLib
  {
    public object Property1
    {
      get { return new object(); }
    }
  }
 

and below is the client's app in the InterfaceApp;

 

namespace InterfaceApp
{
  class Program
  {
    // InterfaceApp is the consumer of the interface that is defined inside the 
    // InterfaceLib
    // to test the compatibility
    // we better compile the version 1 of InterfaceLib, copied to somewhere
    // add the reference to lib from the copied address
    // later, we try to runtimely load the version 2 of the interfaceLib
    static void Main(string[] args)
    {
      IInterfaceLib lib = new InterfaceLibImpl();
      if (null != lib.Property1)
      {
        Console.Write("You should always see this line");
      }

      Console.Read();

    }
  }
}
 

now, suppose, the lib maintainer decide to add another property to the InterfaceLib, and add a new property called Name to the interface IInterfaceLib; we assume lib maintainer has all impl to that interface to his control (as in the precondition sectinon); now the IInterfaceLib has changed to this:

 

  public interface IInterfaceLib
  {
    object Property1 { get; }
    string Name { get; } // in v2.0 we decide to add new property that is called Name
  }
  public class InterfaceLibImpl : IInterfaceLib // now v2.0 impl to IInterfaceLib
  {
    public object Property1
    {
      get { return new object(); }
    }
    public string Name
    {
      get { return "InterfaceLibImpl"; }
    }
  }
 

 

we do not need to compile, InterfaceApp, just replace the reference with the new one, and then program run as usual. So adding a new member does not break the compatibility;

 

Let's relax the constraint a little bit; Let's suppose the use has written impl to the IInterfaceLib interface; (it is against the InterfaceLib v1.0)

 

  // below is a client's impl to IInterfaceLib againt InterfaceLib v1.0
  class AppInterfaceLibImpl : IInterfaceLib
  {
    object m_property1; 
    public AppInterfaceLibImpl(object param)
    {
      m_property1 = param;
    }
    public object Property1
    {
      get { return m_property1; }
    }
  }
 

and now the client's app is becoming this: 

 

  class Program
  {
    static void Main(string[] args)
    {
      IInterfaceLib lib = new InterfaceLibImpl();
      if (null != lib.Property1)
      {
        Console.Write("You should always see this line");
      }

      AppInterfaceLibImpl appImpl = new AppInterfaceLibImpl(lib.Property1);
      if (null != appImpl.Property1)
      {
        Console.Write("you shall see this as well");
      }
      Console.Read();

    }
  }

 

First, compile, it is OK, but what if we run? boom/bang!, it fails at runtime. Below is the common error that you get 

 

Method 'get_Name' in type 'InterfaceApp.AppInterfaceLibImpl' from assembly 'InterfaceApp, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
 

 

 

 

So a general rule for adding member to an interface is  as follow, 

 

NO, YOU DO NOT ADD MEMBER TO EXISTING INTERFACS!

 

But sometimes, you have to, how?

 

An always true rule for adding new mebers

 

 

All the discussion above has a big assumption, that the lib maintainer has the control over the impls to that interfaces, however, if the interface is a public interface, you cannot just simply took that no client would ever write impls for that interface, and if you add new members, you may break user's code;

 

So a common technique is to create a new interface, inherit the new interface, and add the new members there;

 

  public interface IInterfaceLib
  {
    object Property1 { get; }

  }

  public interface IInterfaceLib2 : IInterfaceLib
  {
    string Name { get; } // in v2.0 we decide to add new property that is called Name
  }
 

the lib's impl will now can point to the new interface

 

  public class InterfaceLibImpl : IInterfaceLib2 // now v2.0 impl to IInterfaceLib
  {
    public object Property1
    {
      get { return new object(); }
    }
    public string Name
    {
      get { return "InterfaceLibImpl"; }
    }
  }

 

 

and the client's impl stay the same;

 

 

This can satisify both the compiler and runtime (for both the client and lib developer)

 

Removing an existing member

 

Rename a member

 

 

change signaure / order 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics