codewarrior
codewarrior

Reputation: 991

How to restrict access to a nested class to its container in C#?

I have a configuration class store application configuration. Currently I am using a static class. Some of the configurations are related to one topic so I want to organize them into a nested class, so I can reference configurations like this:

AppConfig.Url
AppConfig.LogSettings.FileSize

I have two options, either use a static nested class,

public static class AppConfig
{
    public static class LogSettings
    {
        public static int FileSize {get; set;}
    }
}

or declare a class but add a static property:

public static class AppConfig
{
    public class LogSettings
    {
        public int FileSize {get; set;}
    }

    public static LogSettings logSettings { get; private set; }
}

However, none of them can protect nested class member FileSize being modified by other classes, even I use private set to protect the public static property. Maybe I should not use nested class to implement this? Any suggestions?

Upvotes: 3

Views: 2027

Answers (4)

wallismark
wallismark

Reputation: 1856

I voted for Eric's answer but wanted to put this here as 'something to consider'.

public class Tester
{
    public Tester()
    {
        AppConfig.LogSettings.FileSize = 5; // compile error 
        Console.WriteLine(AppConfig.LogSettings.FileSize); // works
    }
}

public static class AppConfig
{
    // just an example of setting the value in the outer class
    private static void SetFileSize(int size)
    {
        fileSize = size; // internal only setting works
    }

    private static int fileSize; // a member of AppConfig still

    public static class LogSettings
    {
        public static int FileSize
        {
            get { return fileSize; } // internal classes can access private members of the outer class
        }
    }
}

When you access AppConfig externally you get the grouping you desire, however inside the AppConfig class the 'grouping' is simply about publicly exposing the getter, the actual member variable still belongs to AppConfig.

This works because the internal class can access private members of the outer class.

So if your grouping goal is mostly about the public interface - how you get the values - this approach is simpler, but if your goal is also have the grouping internally too...then obviously it doesn't deliver.

Upvotes: 2

Eric Lippert
Eric Lippert

Reputation: 660407

The other solutions given so far essentially require the property being set to be set once. I am very much in favour of immutable objects, but they are not always practical; is it possible to solve your problem and make the property mutable?

This is in fact a special case of the more general problem you pose: how can we mark certain members of an inner class as being accessible only to the outer class, but not to anything outside of that class?

It is by no means obvious how to do so, but this is surprisingly easy. The key to the solution is to remember that interfaces may be private implementation details. C# requires that a base class be at least as accessible as the class deriving from it, but C# does not require that an implemented interface be as accessible as the class implementing it!

using System;
public static class Outer
{
    private interface IPrivates
    {
        string Name { set; }
    }
    public readonly static Inner TheInner = new Inner();
    private readonly static IPrivates TheInnerPrivates = TheInner;
    public class Inner : IPrivates
    {
        public string Name { get; private set; }        
        string IPrivates.Name { set { this.Name = value; } }
    }
    public static void DoIt()
    {
        TheInnerPrivates.Name = "abc";
    }
}  
public class Program
{
    public static void Main()
    {
        Outer.DoIt();
        Console.WriteLine(Outer.TheInner.Name);
    }
}

Code inside of Outer may access the private members of Inner via the interface. Code outside of Outer cannot see anything other than the public members of Inner, because the interface they need to see the private members is itself private.

Upvotes: 12

Evk
Evk

Reputation: 101563

One option is to use a constructor of nested class to initialize values. For example:

public static class AppConfig {
    static AppConfig() {
        Log = new LogSettings(1);
    }

    public class LogSettings {
        public LogSettings(int fileSize) {
            FileSize = fileSize;
        }

        public int FileSize { get; private set; }
    }

    public static LogSettings Log { get; private set; }
}

Then other classes can create instance of LogSettings still, but cannot modify your instance.

Upvotes: 1

peter
peter

Reputation: 15129

If this is the approach you want to go with, I see 2 options:

  1. You extract all the logic surrounding loading and parsing of the config file into a separate project (library), than you can mark your setters internal and other libraries won't be able to access them anymore.

  2. You can implement a more explicit property which doesn't allow to set a value twice like:

    private int? _fileSize;
    
    public int FileSize { 
        get { return _fileSize ?? 0; }
        set {
            if (_fileSize.HasValue) {
                throw new InvalidOperationException("You can only set the value once");
            }
            _fileSize = value;
        }
    }
    

Another option, which I've often used, is to encapsulate the parsing of different parts of a configuration to the sub classes. You provide that information in the constructor and the class initializes itself. (just like Evk commented in the meanwhile)

Upvotes: 0

Related Questions