Jake
Jake

Reputation: 11430

C# interface with internal setters

Background: Picture an artwork of a shape where within it is multiple levels of nested shapes. Change of properties e.g. area and length of any of those shape-within-a-shape will cause all the related properties and shapes to change.

I have a design pattern which goes like this:

I have an object graph called (for discussion sake) "NestedShapes" that has tons of properties which are related to each other, for example, "Area" and "Length". But the graph is designed to be dumb, i.e. given either value, it doesn't know how to calculate the other and will not do so.

What happens instead is that the graph can be attached to a GraphManager which takes the top level root node IRootShape in its contructor.

NestedShapes implements IRootShape which also implements INotifyPropertyChanged. GraphManager subscribes to those property changes, and runs the logic to calculate related fields and set the graph to the correct state via IRootShape.

Problem: Along with IRootShape, I have IShape, ISquare, ICircle etc. which are real C# interfaces. But the problem is for some of these properties I only want them to have setters that are private to GraphManager. I know the implementing shape can still expose a public setter, but I do not want to necessarily expose these on the UI side to be able to set the property from GraphManager. What should I do? Is base classes with internal set the way to go?

Upvotes: 1

Views: 2627

Answers (2)

Mark A. Donohoe
Mark A. Donohoe

Reputation: 30368

The trick to do this via interfaces is to use two separate interfaces; one public and a second which is internal-only. Yes, it requires a little more code as you have to explicitly implement the internal interface, manually delegating its property contracts down to the actual properties, but I just hide that in a partial class in a file called 'ClassName.Internal.cs'

This way you can clearly and cleanly expose the interfaces with the exact permissions that you want, all without having to resort to base classes, so this technique can be used to extend existing object graphs as well.

Here's an example of a TestItem with public getters and internal setters, all accessible via interfaces.

Here's the main class file with the public interface, stored in TestItem.cs

public interface ITestItem
{
    ModelItem  Owner { get; }
    ModelScope Scope { get; }
}

public partial class TestItem : ITestItem
{
    // These implicitly handle ITestItem since the getters are public
    public ModelItem  Owner { get; internal set; }
    public ModelScope Scope { get; internal set; }
}

Here's the internal-only implementation stored in TestItem.Internal.cs implemented via a partial class.

internal interface IWritableTestItem
{
    ModelItem  Owner { get; set; }
    ModelScope Scope { get; set; }
}

public partial class TestItem : IWritableTestItem
{
    ModelItem IWritableTestItem.Owner
    {
        get => Owner;
        set => Owner = value;
    }

    ModelScope IWritableTestItem.Scope
    {
        get => Scope;
        set => Scope = value;
    }
}

Hope this helps!

Upvotes: 1

Daryl Teo
Daryl Teo

Reputation: 5495

Let GraphManager interact with the Base classes.

Everything else interacts with the interfaces only.

Do not expose properties in your interface.

public class Circle : ICircle{
   public double Radius{
      get;set;
   }

   /* blah blah ... */
}

public interface ICircle {
   /* No properties */

   /* blah blah ...*/
}

Upvotes: 2

Related Questions