Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I disallow other assemblies from inheriting from a class?

Tags:

c#

I've got something like this:

// This gets implemented by plugin authors to get callbacks about various things.
public interface ExternalPlugin
{
    // This gets called by the main application to tell the plugin some data
    // is available or similar.
    void DoStuff(SomeDataBlob blob);
}

// Data blob for v1 of API
public class SomeDataBlob
{
    internal SomeDataBlob(string prop) { Prop = prop; }
    // Some piece of data that V1 plugins need
    public string Prop { get; private set; }
}

// FUTURE!
// Data blob API v2 of API
public class SomeDataBlobV2 : SomeDataBlob
{
    // Can be passed to clients expecting SomeDataBlob no problem.
    internal SomeDataBlobV2(string prop, string prop2) :base(prop) { Prop2 = prop2; }
    // Some piece of data that V2 plugins need. V2 plugins can cast to this from
    // SomeDataBlob, but still can load successfully into older versions that support
    // only V1 of the API
    public string Prop2 { get; private set; }
}

I have to make SomeDataBlob public so that it can be used as a member of the public interface method ExternalPlugin.DoStuff. However, I would not like to allow clients to inherit from that class and thus be susceptible to the brittle base class problem. (All derivatives of that class should be kept in the same assembly)

Marking the class sealed goes too far because I believe removing sealed is a breaking API change; and even if that isn't, once I ship SomeDataBlobV2 clients could still do the wrong thing and inherit from SomeDataBlob directly.

Is there a way to enforce this kind of pattern?

like image 340
Billy ONeal Avatar asked Jul 02 '13 20:07

Billy ONeal


2 Answers

Make the class internal, and expose an interface instead. Then use the factory pattern to create the correct implementation.

public interface ISomeDataBlob
{
}

internal class SomeDataBlob : ISomeDataBlob
{
}

public class BlobApiFactory
{
    ISomeDataBlob Create();
}

You hide the implementation, but still give the user access to everything. You even make unit tests easier for your users ;)

Edit (answer to a comment from the OP)

What I effectively want is some method taking some parameters. I want to be able to add parameters that the main application can provide in a future version if the API without breaking clients. But I don't want clients to be able to create instances of the "parameters" class/interface or otherwise interact with it beyond receiving an instance of it as a parameter

Instead of hiding the APIs you can make sure that all object passed to your library originates from your assembly:

public class YourCoolService
{
    public void DoSomething(ISomeDataBlob blob)
    {
        if (blob.GetType().Assembly != Assembly.GetExecutingAssembly())
            throw new InvalidOperationException("We only support our own types");
    }
}

Edit2

Just noticed that @RQDQ already provided that solution (didn't notice when answering your comment). If that's the solution you want, accept his answer instead.

like image 154
jgauffin Avatar answered Nov 15 '22 14:11

jgauffin


  /// <summary>
  /// This is a dummy constructor - it is just here to prevent classes in other assemblies
  /// from being derived from this class. 
  /// See http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2971840&SiteID=1
  /// </summary>
  internal MhlAdminLayer() { }

The idea is to have a constructor with no parameters internal. Then the class can't be derived from.

Edit: Sorry, the link in the comment doesn't work any more.

Edit 2: http://msdn.microsoft.com/en-us/library/vstudio/ms173115.aspx

"You can prevent a class from being instantiated by making the constructor private ... "

like image 33
RenniePet Avatar answered Nov 15 '22 14:11

RenniePet