-1

The sample code below better explains my issue, its well explained in the comments. Browser contains various implementers of BrowserModule, these modules have a reference to the owner Browser so they can work together internally. Each "main" module has a SpecificModule inside, these "main" modules only serve as a proxy for the specific modules that will implement the target browser automation library like Selenium or Puppeteer etc...

For the BrowserMouse module, it would for example, instantiate the SpecificModule as BrowserPuppeteerChromiumSpecificMouse and delegate the calls to that specific module.

I don't want users of the Browser class to have access to the underlying implementations of each automation library so I hide them that way, but Im having problems as the Mouse position for example can be set by doing Browser.Mouse.PositionOnDocument = new Point(123,123), that ability I would like to be only available internally(namespace level) and not to the end user of the library.

What should be allowed is for example, another module wants to change the position of the Mouse, it could change it normally just accessing Browser.Mouse.PositionOnDocument = new Point();

But that should only be allowed from the same namespace, not from outside use, outside use should be read only.

using System;
using System.Drawing;

namespace Test
{
    using InternalNamespace;

    class Program
    {
        public static void Main()
        {
            Browser browser = new Browser();
            browser.Initialize();

            Console.WriteLine(browser.Mouse.PositionOnDocument);

            // This should NOT be allowed because its being used from outside the Browser namespace
            browser.Mouse.PositionOnDocument = new Point(2, 2);

            Console.WriteLine(browser.Mouse.PositionOnDocument);

        }
    }
}

namespace InternalNamespace
{
    public class Browser
    {
        public BrowserMouse Mouse;

        public void Initialize()
        {
            // Instantiate the main mouse with underlying specific puppeteer module
            // pass a null module to the specific module because it doesnt need 
            // one underlying specific module
            Mouse = new BrowserMouse(this, new BrowserPuppeteerChromiumSpecificMouse(this, null));

            // This is OK because its inside the namespace
            Mouse.PositionOnDocument = new Point(1, 1);
        }
    }

    public class BrowserModule
    {
        // ExternalBrowser that owns this module.
        protected Browser Browser { get; set; }

        // Underlying specific module => Puppeteer, Selenium or anything.
        protected BrowserModule SpecificModule { get; set; }

        public BrowserModule(Browser browser, BrowserModule specificModule)
        {
            Browser = browser;

            if (specificModule != null)
                SpecificModule = specificModule;
        }
    }

    interface ILocatable
    {
        Point PositionOnDocument { get; set; }
    }

    // This whole class just servers as a proxy for the underlying module.  
    public class BrowserMouse : BrowserModule, ILocatable
    {
        public BrowserMouse(Browser browser, BrowserModule specificModule) : base(browser, specificModule)
        {
        }

        // This delegates the call to the specific module.
        public Point PositionOnDocument
        {
            get
            {
                return ((ILocatable)SpecificModule).PositionOnDocument;
            }
            set
            {
                ((ILocatable)SpecificModule).PositionOnDocument = value;
            }
        }
    }

    // The specific module implementation that is gonna be called from the main module.
    public class BrowserPuppeteerChromiumSpecificMouse : BrowserModule, ILocatable
    {
        public BrowserPuppeteerChromiumSpecificMouse(Browser browser, BrowserModule specificModule) : base(browser, specificModule)
        {
        }

        public Point PositionOnDocument { get; set; }
    }
}
Joao Vitor
  • 169
  • 1
  • 9
  • Do you control the interface? – V0ldek Jan 04 '20 at 19:58
  • 1
    The reason V0ldek is asking that is because if you control the interface, you can remove the `set;` part of the property to make it read-only. – Powerlord Jan 04 '20 at 20:00
  • Yes i do. But if i remove the setter, i need to create a backing field inside each implementer of that interface, correct? – Joao Vitor Jan 04 '20 at 20:03
  • @Joso Vitor No, get-only auto properties are supported since C#6 – V0ldek Jan 04 '20 at 20:08
  • @V0ldek well, the implementer specific classes will need to change this position, if the property is get only i need a field that will be set by these specific classes – Joao Vitor Jan 04 '20 at 21:45

2 Answers2

1

This interface allows implementers to expose a PositionOnDocument

Not really, an interface requires implementers to fulfill a contract the contract is that there's a property PositionOnDocument with a public getter and setter. If you cannot change the interface, you cannot implement it without the setter - you cannot break the contract.

If you can change the interface, remove the setter from the contract.

interface IExternalBrowserLocatable
{
    Point PositionOnDocument { get; }
} 

If you want a setter accessible internally, make its visibility internal.

internal class ExternalBrowserPuppeteerChromiumSpecificMouse : ExternalBrowserPuppeteerChromiumSpecificModule, IExternalBrowserLocatable
{
    ... 
    public Point PositionOnDocument { get; internal set; }
}

EDIT:

If you want a common interface for the setters that is available only internally, you can do just that:

// You can probably come up with a better name.
internal interface IExternalBrowserLocatableMutable : IExternalBrowserLocatable
{
    new Point PositionOnDocument { get; set; }
}

internal class ExternalBrowserPuppeteerChromiumSpecificMouse : ExternalBrowserPuppeteerChromiumSpecificModule, IExternalBrowserLocatableMutable
{
    ... 
    public Point PositionOnDocument { get; set; }
}

It's perfectly legal (and very useful!) for a public class to implement an internal interface, and you can cast the implementing class to either the direct internal interface or its public base. When accessing through the internal interface (or the class itself), you get both the getter and the setter. The public interface exposes only the getter.

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • Ok, I have tried that before, but the problem is that (TLDR) all of these "modules" (mouse, keyboard etc..) will derive from ExternalBrowserModule which contains a reference to the ExternalBrowser that owns it. I need that each module can have access the the other modules inside them. Think as of an example, the ExternalBrowserScroller would need to set the position of the mouse because when you scroll the browser window with your mouse wheel the mouse pointer follows it... And without a setter on the interface member I cant use it from the main classes, cuz only specific classes use the "set" – Joao Vitor Jan 04 '20 at 21:54
  • I can do inside the specific scroller, for example ExternalBrowserPuppeteerChromiumSpecificScroller, "ExternalBrowser.Mouse.PositionOnDocument = x", but thats not possible since only the underlying SpecificModule uses an internal setter, the "Main" class does not, remember that there are two "Mouse" classes, one that fires the method on the SpecificModule and the SpecificModule itself. – Joao Vitor Jan 04 '20 at 21:56
  • @JoaoVitor So you want to have your classes share a common interface that exposes the `internal` setter, but in a way that doesn't make it accessible from the outside? – V0ldek Jan 04 '20 at 22:50
  • Edited to support the above ^ – V0ldek Jan 04 '20 at 23:01
  • So the ExternalBrowserMouse should implement IExternalBrowserLocatable, and ExternalBrowserPuppeteerChromiumSpecificMouse should implement IExternalBrowserLocatableMutable? Is it normal that when implementing the mutable one it implements both explicit and implicitly? – Joao Vitor Jan 05 '20 at 00:42
  • @Joao Vitor It's okay to implement the `internal` one on whatever type you expect to use internally via the interface. And I don't understand the second question - when you implement an interface you have to implement all of the interfaces up in its inheritance hierarchy as well. – V0ldek Jan 05 '20 at 00:47
  • Im saying that when I right click and select "implement interface" on the quick actions menu, it creates two properties for me. one is "public Point PositionOnDocument" with get and set. The other property "Point IExternalBrowserLocatable.PositionOnDocument => throw new NotImplementedException();", so it does both the implicit and explicit implementations, this got me confused on how I would write the implementation – Joao Vitor Jan 05 '20 at 02:11
  • @Joao Vitor You don't need the explicit implementation, one property is sufficient to implement both interfaces. I made a [short demo](https://dotnetfiddle.net/ipMfqP) to illustrate the behaviour of a similar hierarchy. – V0ldek Jan 05 '20 at 02:16
  • Thanks, any idea why VS does that, implementing both explicit and implicit? – Joao Vitor Jan 05 '20 at 02:17
  • @JoaoVitor Probably because it guesses that you might want that - you could make the explicit implementation do something completely different than the one for internal use. – V0ldek Jan 05 '20 at 02:20
  • to understand better my problem, please. I created a demo for you too check it out. https://dotnetfiddle.net/yRuYLX – Joao Vitor Jan 05 '20 at 02:55
  • @JoaoVitor In that sample you're never exposing your `ILocatable` interface. It looks as if that interface was not central to your question, as the only place you use it in is the cast at lines 78 and 82. A cast which is also disastrous, as `BrowserModule` is not required to implement that interface, so this is a possible `InvalidCastException` waiting to happen. Can you edit your question around that dotnetfiddle sample? It gives a better picture. Try to clearly state which parts are required public surface you want to provide and which ones are your attempts at solving the problem. – V0ldek Jan 05 '20 at 03:10
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/205369/discussion-between-joao-vitor-and-v0ldek). – Joao Vitor Jan 05 '20 at 03:21
0

First of all, internal access disallows access from outside the assembly, not from a different namespace. It's perfectly legal to refer to internal members from a completely different namespace in the same assembly, and illegal from a different assembly even within the same namespace. I'll assume you want to restrict the access in the .NET internal sense, as the namespace-specific behaviour you're describing is not possible.

Your ILocatable interface is superficial. It serves only so that you can internally do the cast (ILocatable)SpecificModule, and that cast is possibly disastrous, because BrowserModule doesn't necessarily implement ILocatable, so it's a possible InvalidCastException, which should never happen in correct code.

Since you're exposing BrowserModule to the public anyway, and you clearly expect any BrowserModule to have the property PositionOnDocument, turn it into an abstract class with an abstract property with an internal setter:

public abstract class BrowserModule
{
    // ExternalBrowser that owns this module.
    protected Browser Browser { get; set; }

    // Underlying specific module => Puppeteer, Selenium or anything.
    protected BrowserModule SpecificModule { get; set; }

    public abstract Point PositionOnDocument { get; internal set; }

    ...
}

public class BrowserMouse : BrowserModule
{
    ...

    // This delegates the call to the specific module.
    public override Point PositionOnDocument
    {
        get
        {
            return SpecificModule.PositionOnDocument;
        }
        internal set
        {
            SpecificModule.PositionOnDocument = value;
        }
    }
}

// The specific module implementation that is gonna be called from the main module.
public class BrowserPuppeteerChromiumSpecificMouse : BrowserModule
{
    ...

    public override Point PositionOnDocument { get; internal set; }
}
V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • Thanks, I tried with abstract classes and also without abstract classes, but I noticed I would need to implement a PositionOnDocument on all modules or items that need to be found inside the browser, probably I gave the impression that ALL modules should have a PositionOnDocument, which is not true, the Mouse and HtmlElement are classes that need that position for example, the "Navigator" which lets the browser visit pages should not need it, for example, so it makes sense to make the BrowserModule abstract, derive the BrowserMouse from it and place a PositionOnDocument to be overriden... – Joao Vitor Jan 05 '20 at 15:39
  • 1
    @JoaoVitor Then why does your code in `BrowserMouse` assume that any module must implement `ILocatable`? This started as a specific, technical "Implement interface without publicly exposing property setter?" question, which can -- and was -- reasonably answered, but now it seems that you should just take your design back to the drawing board and come up with a better hierarchy. I recommend you do just that, and if while doing so you find a specific problem that can be phrased as an SO question, ask another one. – V0ldek Jan 05 '20 at 17:35