Jenix
Jenix

Reputation: 3076

How can I access private members from other classes?

I'm not that new to C# but don't have as much experience as in Java. As you know, in Java, we can access all the private members from outer classes. So I tried the same thing in C# because I had some fields and methods needed to be accessed from only inside my plugin library and didn't want it to be shown to users. A simple example can be like this.

public static class StaticClass {

    public class InstanceClass {
        private int oldValue;
        public int Value;
    }

    public static void Backup(InstanceClass ic) {
        ic.oldValue = ic.Value;
    }

    public static void Restore(InstanceClass ic) {
        ic.Value    = ic.oldValue;
    }
}

If I make the field oldValue public, then it'll be mess and look dirty when end users use the plugin. It doesn't have to be an Inner class or in a some specific form. I just want to know if there is any way to control or access private members of an instance from other static classes in the same assembly only by me.

Upvotes: 0

Views: 8005

Answers (6)

Kasper van den Berg
Kasper van den Berg

Reputation: 9526

This field oldValue is an implementation detail of both StaticClass and InstanceClass. Lets make InstanceClass an implementation detail of StaticClass and export an interface StaticClass.IInstance to external clients:

public static class StaticClass {
     public interface IInstance {
         int Value { get; set; }
     }

     private class InstanceClass: IInstance {
         public int oldValue;
         public Value { get; set; }
     }

     // Static class becomes responsible for handing out `IInstance` objects
     public static IInstance GetInstance() {
         return new InstanceClass();
     }

     public static void Backup(IInstance i) {
         if (i is InstanceClass ic) {
             ic.oldValue = ic.Value;
         }
         else {
             throw new InvallidOperationException("Can only Backup IInstance objects that were created by GetInstance");
         }
     }

     public static void Restore(IInstance i) {
         if (I is InstanceClass ic)
         {
             ic.Value = ic.oldValue;
         }
         else {
             throw new InvallidOperationException("Can only Restore IInstance objects that were created by GetInstance");
         }
     }

This solution is similar to the one Luaan proposes. But instead of using an interface to export private data, it uses an interface to limit the publicly available data; to my opinion this is a cleaner design with less surprises.
It does change Value from a field to a property; so when you really need a field, this pattern does not work.

The static class in the example of OP makes it a bit awkward and having better solutions, but imagine this in a regular class, perhaps in a repository. Working on a repository, where observers should be notified when properties of items in the repository are set and not wanting the items to contain a reference to the repository or to the repositories observers, led me to searching for "method only accessible to container class?" which led me to this question.

I intend to solve it as follows:

public class Repo
{
    public interface IItem
    {
        int Id { get; }
        string MyProperty { get; }
    }

    private class Item
    {
        public int Id { get; }
        public string MyProperty { get; private set; }

        public bool TrySetMyProperty(string newValue)
        {
            if (!Equals(MyProperty, newValue) &&
                IsPreconditionValid())
            {
                MyProperty = newValue;
                return true;
            }
            else
            {
                return false;
            }

            IsPreconditionValid() => true;
        }
    }


    public event EventHandler<EventArgs> OnChanged;

    private readonly ConcurrentDictionary<int, Item> items = new ConcurrentDictionary<int, Item>();

    public IItem GetOrCreateItemById(int id)
    {
        bool changed = false;
        IItem result = items.GetOrAdd(int, CreateItem);
        if (changed)
        {
            OnChanged?.Invoke(this, EventArgs.Empty);
        }
        return result;

        IItem CreateItem(int key)
        {
            changed = true;
            return new Item() { Id = key };
        }
    }

    public bool TrySetItemMyProperty(int id, string newValue)
    {
        if (items.TryGet(id, out Item i))
        {
            if (i.TrySetMyProperty(newValue))
            {
                OnChanged?.Invoke(this, EventArgs.Empty);
                return true;
            }
        }
        return false;
    }
}

Upvotes: 0

Luaan
Luaan

Reputation: 63772

This is not possible in C#. The container class has no special access over the nested class.

You can access private members of the container from the nested class, but not vice versa. The pattern you're trying to use simply isn't used in C# - it's a violation of member accessibility. There are some hacks to force the Java pattern on C# (using reflection or abusing interfaces), but they are just that - hacks.

The "cleanest" approach might look something like this:

public static class StaticClass 
{
  private interface IInstanceClassInternal
  {
    int OldValue { get; set; }
  }

  public sealed class InstanceClass : IInstanceClassInternal 
  {
    int IInstanceClassInternal.OldValue { get; set; }

    public int Value;
  }

  public static void Backup(InstanceClass ic)
  {
    ((IInstanceClassInternal)ic).OldValue = ic.Value;
  }

  public static void Restore(InstanceClass ic) 
  {
    ic.Value = ((IInstanceClassInternal)ic).OldValue;
  }
}

It's obvious that you're trying to write Java in C# - the patterns, the coding style... That's probably a bad idea. Those static methods should probably be extension methods. The "hidden functionality in an object" doesn't quite sit with C#'s notion of OOP - your parent shouldn't have free access to your guts, it should only really have the same public interface everyone else has. After all, that's the whole point of LSP - such tight coupling is quite tricky for any extensibility. Why separate StaticClass from InstanceClass in the first place, if you want StaticClass to mess with InstanceClasses privates? Just make Backup and Restore public members of InstanceClass - or even a part of an interface (perhaps even through explicit implementation, if you want to "hide" it from users of InstanceClass).

Upvotes: 3

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186833

Technically, you can use Reflection (if you insist on private field and a static class methods):

using System.Reflection;

... 

public static void Backup(InstanceClass ic) {
  if (null == ic)
    throw new ArgumentNullException("ic");

  ic.GetType()
    .GetField("oldValue", BindingFlags.NonPublic | BindingFlags.Instance)
    .SetValue(ic, ic.Value);
}

public static void Restore(InstanceClass ic) {
  if (null == ic)
    throw new ArgumentNullException("ic");

  ic.Value = (int) (ic.GetType()
    .GetField("oldValue", BindingFlags.NonPublic | BindingFlags.Instance)
    .GetValue(ic));
}

however, a much better approach is to change access modifier from private to internal:

public class InstanceClass {
    internal int oldValue;
    public int Value;
}

Even better solution is to move both Backup and Restore methods into InstanceClass:

public class InstanceClass {
    private int oldValue;
    public int Value;

    public void Backup() {
      oldValue = Value; 
    }

    public void Restore() {
      Value = oldValue; 
    }
}

Upvotes: 1

Fabio Silva Lima
Fabio Silva Lima

Reputation: 714

Have you tried to make it "internal"? It will be available in same dll but not external dll.

public class InstanceClass {
    internal int oldValue;
    public int Value;
}

Upvotes: 1

matthijsb
matthijsb

Reputation: 909

You can use the internal access modifier, see https://msdn.microsoft.com/en-us/library/ms173121.aspx

Internal is only visible from inside the assembly

Example: https://dotnetfiddle.net/FNavfE

Upvotes: 1

Akshey Bhat
Akshey Bhat

Reputation: 8545

For allowing access only within assembly use internal modifier.

public class InstanceClass {
        internal int oldValue;
        public int Value;
    }

Upvotes: 3

Related Questions