4

Let's say that I am using a library that I have no control over whatsoever. This library exposes service that requires argument of certain class. Class is marked as sealed and has no interface.

tl;dr: How can I reimplement sealed class as interface?

Code example:

using System;

namespace IDontHaveControlOverThis
{
    // Note no interface and the class is being sealed
    public sealed class ArgumentClass
    {
        public String AnyCall() => "ArgumentClass::AnyCall";
    }

    public sealed class ServiceClass
    {
        public String ServiceCall(ArgumentClass argument) => $"ServiceClass::ServiceCall({argument.AnyCall()})";
    }
}

namespace MyCode
{
    // Composite pattern, basically I need: "is a ArgumentClass"
    // Obviously doesn't work - can't extend from sealed class
    public class MyArgumentClass : IDontHaveControlOverThis.ArgumentClass
    {
        private IDontHaveControlOverThis.ArgumentClass arg = new IDontHaveControlOverThis.ArgumentClass();

        public String AnyCall() => $"MyArgumentCLass::AnyCall({arg.AnyCall()})";
    }
}

public class Program
{
    public static void Main()
    {
        // I don't have control over this
        IDontHaveControlOverThis.ServiceClass service = new IDontHaveControlOverThis.ServiceClass();


        //This obviously works
        IDontHaveControlOverThis.ArgumentClass arg = new IDontHaveControlOverThis.ArgumentClass();
        Console.WriteLine($"Result: {service.ServiceCall(arg)}");

        // How to make this work?
        IDontHaveControlOverThis.ArgumentClass myArg = new MyCode.MyArgumentClass();
        Console.WriteLine($"Result: {service.ServiceCall(myArg)}");
    }
}
Jacek Lipiec
  • 412
  • 3
  • 10
  • 1
    Is that the actual implementation of `ArgumentClass`? That would mean that you can only do the servicecall with the same argument? – Jesse de Wit Aug 10 '19 at 12:28
  • Hey Jesse, thanks for the time. I believe that I have already done that - as you can see the signature itself is identical; but I know of no means to actually mark my class as "is a IDontHaveControlOverThis.ArgumentClass". I've tried to search for any relevant examples for my case yet I've found none. – Jacek Lipiec Aug 10 '19 at 12:28
  • Give a better more accurate example of the actual argument class and what you want to do. Your simplified example will only mislead those formulating a solution to your problem. – Nkosi Aug 10 '19 at 12:29
  • @JessedeWit Yes, I've re-created the case I'm working with. Library specifically sealed argument class, argument class has no interface, and service class requires argument of this particular class. – Jacek Lipiec Aug 10 '19 at 12:30
  • 1
    But argumentclass will always return the same value? Or do you have possibilities to change the behavior of argumentclass by setting a property, or creating a new instance with different constructor parameters? If you cannot modify the behavior of argumentclass in any way, the answer is _no_. – Jesse de Wit Aug 10 '19 at 12:32
  • If type checking is done at compile time you could inject IL code. – FCin Aug 10 '19 at 13:10
  • @JacekLipiec any feedback on the provided solutions? – Nkosi Aug 10 '19 at 16:48
  • @Nikosi Sorry, life happened;/ Picked answer – Jacek Lipiec Sep 12 '19 at 20:41

2 Answers2

1

The compiler error message

Cannot implicitly convert type 'MyCode.MyArgumentClass' to 'IDontHaveControlOverThis.ArgumentClass'

note: emphasis mine

should give you a hint as to what you can do

public class MyArgumentClass {
    private IDontHaveControlOverThis.ArgumentClass arg = new IDontHaveControlOverThis.ArgumentClass();

    public String AnyCall() => $"MyArgumentCLass::AnyCall({arg.AnyCall()})";


    public static implicit operator IDontHaveControlOverThis.ArgumentClass(MyArgumentClass source) {

        return source.arg;
    }
}

So now your "wrapper" exposes the 3rd party dependency as needed

 IDontHaveControlOverThis.ArgumentClass myArg = new MyCode.MyArgumentClass();

or directly

var myArg = new MyCode.MyArgumentClass();
Console.WriteLine($"Result: {service.ServiceCall(myArg)}");

Reference User-defined conversion operators (C# reference)

Which can allow for abstracting your code

namespace MyCode {

    public interface IMyService {
        String ServiceCall(MyArgumentClass argument);
    }

    public class MyServiceClass : IMyService {
        public string ServiceCall(MyArgumentClass argument) {
            IDontHaveControlOverThis.ServiceClass service = new IDontHaveControlOverThis.ServiceClass();
            return service.ServiceCall(argument);
        }
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • What's the result from this? Based on what the OP wants, I'd assume it should be `ServiceClass::ServiceCall(MyArgumentCLass::AnyCall(ArgumentClass::AnyCall))`. But wouldn't this solution return `ServiceClass::ServiceCall(ArgumentClass::AnyCall)` since the `MyArgumentClass.AnyCall()` is essentially getting bypassed? – devNull Aug 10 '19 at 12:51
  • @devNull that is correct, I figured this to be an [XY problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). Note my previous comment asking for a better example since this one is misleading – Nkosi Aug 10 '19 at 12:52
1

Based on the code sample you show, the answer is you can't. You need to be able to modify the behavior of IDontHaveControlOverThis.ArgumentClass, by setting a property, or creating a new instance with different constructor parameters in order to modify the servicecall. (It now always returns the same string, so the servicecall is always the same)

If you are able to modify the behavior of the ArgumentClass by setting properties. You could create wrappers for the sealed classes in your own code, and use that throughout your codebase.

public class MyArgumentClass
{
        // TODO: Set this to a useful value of ArgumentClass.
    internal IDontHaveControlOverThis.ArgumentClass InnerArgumentClass { get; }
    public virtual string AnyCall() => "???";
}

public class MyServiceClass
{
    private IDontHaveControlOverThis.ServiceClass innerServiceClass
            = new IDontHaveControlOverThis.ServiceClass();

    public virtual string ServiceCall(MyArgumentClass argument)
    {
        return innerServiceClass.ServiceCall(argument.InnerArgumentClass);
    }
}

or

public class MyArgumentClass
{
    public virtual string AnyCall() => "???";
}

public class MyServiceClass
{
    private IDontHaveControlOverThis.ServiceClass innerServiceClass
            = new IDontHaveControlOverThis.ServiceClass();

    public string ServiceCall(MyArgumentClass argument)
    {
        var serviceArgument = Convert(argument);
        return innerServiceClass.ServiceCall(serviceArgument);
    }

    private IDontHaveControlOverThis.ArgumentClass Convert(MyArgumentClass argument)
    {
        // TODO: implement.
    }
}
Jesse de Wit
  • 3,867
  • 1
  • 20
  • 41