Internal property setters in C#

13,920

Solution 1

The properties in the interface should be read only. It's acceptable for the concrete class that implements the interface to have a setter even if none is defined in the interface.

public interface ICustomer
{
   string FirstName { get; }
   string LastName  { get; }
}

public class Customer : ICustomer
{
   public string FirstName { get; internal set; }
   public string LastName  { get; internal set; }
}

If it's really important that the setter be exposed through an interface, rather than having the interface being entirely read-only, you can use something like this:

public interface IReadCustomer
{
    string FirstName { get; }
    string LastName { get; }
}

internal interface IWriteCustomer
{
    string FirstName { set; }
    string LastName { set; }
}

internal interface IReadWriteCustomer : IReadCustomer, IWriteCustomer
{ }

public class Customer : IReadWriteCustomer
{
    private string _firstName;
    private string _lastName;

    public string FirstName
    {
        get { return _firstName; }
        internal set { _firstName = value; }
    }
    public string LastName
    {
        get { return _lastName; }
        internal set { _lastName = value; }
    }

    string IReadCustomer.FirstName
    {
        get { return FirstName; }
    }

    string IReadCustomer.LastName
    {
        get { return LastName; }
    }

    string IWriteCustomer.FirstName
    {
        set { FirstName = value; }
    }

    string IWriteCustomer.LastName
    {
        set { LastName = value; }
    }
}

Solution 2

I'd like to mark that setter as internal in the interface however, so there's no chance someone implements ICustomer and someone outside the assembly modifies those properties. Is there a good way to do this?

No. Property members are always public, unfortunately. Additionally, messing around with access levels on properties where part of it is specified on the interface gets painful, IIRC. What you can do is this:

public interface ICustomer
{
    string FirstName { get; }
    string SecondName { get; }
}

internal interface ICustomerWithSetMethods : ICustomer
{
    void SetFirstName(string name);
    void SetLastName(string name);
}

public class Customer : ICustomerWithSetMethods

Then from the outside it'll look like Customer only implements ICustomer, but from inside your code will see that it implements ICustomerWithSetMethods.

Unfortunately that doesn't play nicely if your API needs to declare any public methods where you'd really like to just declare a return type of ICustomer, but you'll actually know that it's always ICustomerWithSetMethods.

Assuming you still want to allow multiple implementations, you could potentially go for an abstract class instead:

public abstract class CustomerBase
{
    public abstract string FirstName { get; }
    public abstract string LastName { get; }

    internal abstract void SetFirstName(string name);
    internal abstract void SetLastName(string name);
}

Now we have the slight oddity that no-one outside the assembly can extend your CustomerBase, because there are abstract methods they'd have to override that they can't even see - but it does mean you can use CustomerBase everywhere in your API.

This is the approach we took in Noda Time for calendar systems in the end - I blogged about it when I first came up with the plan. I generally prefer interfaces to abstract classes, but the benefit here was significant.

Share:
13,920
larryq
Author by

larryq

Updated on June 01, 2022

Comments

  • larryq
    larryq almost 2 years

    I'm trying to figure out a good way to approach this. I have a Customer class which implements the ICustomer interface. This interface has a number of properties in it:

    public interface ICustomer
    {
    
       string FirstName {get; set;}
       string LastName  {get; set;}
    }
    

    I only want certain classes to have the ability to set those properties however; namely, those classes in the project. So I thought about making the setter internal:

    public class Customer : ICustomer
    {
    
       string FirstName {get; internal set;}
       string LastName  {get; internal set;}
    }
    

    I'd like to mark that setter as internal in the interface however, so there's no chance someone implements ICustomer and someone outside the assembly modifies those properties. Is there a good way to do this?

  • larryq
    larryq over 11 years
    Thanks for the assist. If I do that however, and the consuming class uses ICustomer it can't get to the setter.
  • Jon B
    Jon B over 11 years
    You missed the types and scope on FirstName and SecondName
  • SAJ14SAJ
    SAJ14SAJ over 11 years
    @larryq That is the whole point, because the interface is not committing to the setter. If you want to implement a second internal interface that adds the setter you could. I often have a series of interfaces: this is a real example: IDimensionedMap only commits to dimensions of a 2D entity; IReadMap with readable properties, IReadMap : IDimensionedMap for read only or computed maps, and then IWriteMap : IReadMap. But this is a complex family of extendable utility classes. (These are 2d Hilbert spaces, not Dictionaries, if you are curious.)
  • SAJ14SAJ
    SAJ14SAJ over 11 years
    @JonSkeet I agree with your approach, but I generally implement both the interface and the abstract base classes when maintainability over time is going to be an issue. Its a bit of extra typing, but tools like Resharper make it easier, and that means you can have more than one abstract implementation when you have a complex family of objects, perhaps one that will be extended outside your own code library.
  • Servy
    Servy over 11 years
    @larryq While I'm not sure if you need it or not, I've added an alternate implementation in which the interface defines an internal set method.
  • Jon Skeet
    Jon Skeet over 11 years
    @SAJ14SAJ: It's not a matter of extra typing - I've love to be able to put an interface over the top of this; the problem is that you can't declare an internal member in the interface. That's the point of this question. (In the case of Noda Time, I really don't mind 3rd parties not being able to add new implementations. We deliberately hide a lot of the types which have to be in abstract member signatures.)
  • SAJ14SAJ
    SAJ14SAJ over 11 years
    @JonSkeet Sorry, I missed that subtlty. I thought there was a second internal interface extending the first public interface, which is legal. Generally C# comes closer than any other classic object language to expressing what I want in that paradigm! My compromise for those cases is also seen in some of the CLR library classes--throwing an IllegalOperationException for the non-supported part of the interface, providing properties that let you test for that first, and documenting the bleep out of it.
  • Jon Skeet
    Jon Skeet over 11 years
    @SAJ14SAJ: That doesn't help when you need to refer to internal types within the member signatures though, unfortunately... I do seem to hit corner cases like this more than others!
  • SAJ14SAJ
    SAJ14SAJ over 11 years
    Yeah, C# doesn't have a friend notion like C++ if I remember right from 20 years ago when I knew C++. Truthfully, I think that is probably the right design decision, but it would have solved this very particular case.
  • larryq
    larryq over 11 years
    Thanks gentlemen, these are great tips and much appreciated.