views:

70

answers:

3

I'm using C#, with which I don't have a lot of experience (I've mostly worked with java/php/javascript so far)

What I want is a class in which I save some data, this data can only be written by one other class, but still be read by other classes in the program.

Something like this:

public class DataObtainer{
 DataItem[] Items;
 public DataObtainer(){
  Items = new DataItem[20];
 }
 public void Update(){
  Items[0].SomeProperty = 5;//Being able to change SomeProperty
 }
 //Class only contains properties
 public class DataItem{
  public int SomeProperty;
 }
}

public class AnyOtherClass{
 public void SomeMethod(){
  DataObtainer do = new DataObtainer();
  //What I want:
  DataItem di  = do.items[0];
  Console.WriteLine(di.SomeProperty);//Being able to read SomeProperty
  di.SomeProperty = 5;//Not allow this, not being able to change SomeProperty
 }
}
A: 

You should make DataItem an outer (non-nested) abstract class, then make an inner (private) class that inherits it and provides public mutator methods.

In DataObtainer, you can then cast the objects to the private inherited class and modify them.

SLaks
+2  A: 

Use an interface.

public interface IData
{
   string Data1 { get;}
   int MoreData { get;}
}

class Data : IData
{
   public string Data1 { get; set;}
   public int MoreData {get; set;}
}

public class DataObtainer
{
   private Data[] items;
   public DataObtainer()
   {
      items = new Data[20];
   }
   public IEnumerable<IData> Items
   {
      get
      {
         return items;
      }
   }

   public void Update()
   {
      Items[0].MoreData = 5;//Being able to change MoreData
   }
}

public class AnyOtherClass
{
   public void SomeMethod()
   {
       DataObtainer do = new DataObtainer();
       //What I want:
       IData di  = do.Items.First();
       Console.WriteLine(di.MoreData);//Being able to read SomeProperty
       di.SomeProperty = 5;//this won't compile
   }
}

Explaination:

  1. Create an interface which you are going to provide to code (IData)
  2. Create in implementation of that interface
  3. Store in the Obtainer the implementation
  4. Give other code access only to the interface. This doesn't give them access to change values.
  5. The calling code can cast to the implementation if it wants, but then they are breaking the contract and all bets are off.
tster
If you make the concrete implementation a private inner class other classes won't be able to cast to it
Rune FS
Thank you very much! I've made the "Data" class a private inner class as Rune FS said, and everything works like a charm!I have one more question though, in your example you use "IEnumerable<IData> Items", and I'm wondering if and why this is better than just "IData[] Items"?
Wander
@Wander, If you give them access to IData[] then they can take that and do something like: `obtainer.Items[1] = differentData;`. My assumption is that you don't want them changing the objects within the obtainer.
tster
@Rune FS, yes that's true. Although I mostly don't worry about people casting to a base type and breaking the contract. Even with that they can still use reflection to change the values if they really want to.
tster
@tster The reflection part will only work with sufficeint trust between the defining assembly and the reflecting one so it is in other words possible to "seal it tight" IEnumeration<T> is not specifying that you can't change the sequence but only stating that you cannot and should not rely always being able to. If you return an array you can still change the elements (yes you'd need to cast) if you do what an immutabl sequence you a type for that (possibly in conjunction with IEnumerabl<T> to define the contract)
Rune FS
A: 

That sort of design seems awkward to me. You are in control of the code, so what you are asking to do is not really necessary unless you are designing some sort of framework/api. If a class shouldn't be able to modify a property, don't modify the property or don't provide a setter.

Maybe if you can explain a bit more of what or why you need to accomplish this to help us understand the best approach to provide you for your goal here.

Simple Example using basic inheritance

// Class used to read and write your data
public class DataBuilder : Data {
    public void SetValue(int value) {
        base.m_SomeValue = value; // Has access to protected member
    }
}

// Class used to store your data (READONLY)
public class Data {
    protected int m_SomeValue; // Is accessible to deriving class
    public int SomeValue {     // READONLY property to other classes
                               // EXCEPT deriving classes
        get {
            return m_SomeValue;
        }
    }
}

public class AnyOtherClass {
    public void Foo() {
        DataBuilder reader = new DataBuilder();
        Console.WriteLine(reader.SomeValue); // *CAN* read the value
        reader.SomeValue = 100; // CANNOT *write* the value
    }
}
David Anderson
I disagree. You shouldn't have to just remember not to set something. However, the language provides this ability. In fact, this is pretty much the best use case for inheritance.
tster
The reason this seems awkward is because if you don't need to set something, or don't want to set something, it shouldn't even be an issue because it's already part of the design. Whereas if this were a framework or an API, then it does open a security/design risk where data can be modified that probably shouldn't be. But that was the entire point of properties, to hide the private member that stores the data so you can control the data and not expose those type of risks. Inheritence, yes is a good method for doing this type of thing, but I wasn't questioning your answer either.
David Anderson
Thank you very much for your time and help. The reason I want the properties to be read-only from other classes is because it is merely a representation of data obtained somewhere else. Changing the properties will not change the data somewhere else.If I would change the properties in other classes, it would not be a proper representation anymore, so even though I can just not-change the properties anywhere, I rather have the code not allowing so at all.Your example works great, however I decided to go with the interface way tster posted, as it requires less extra methods and properties.
Wander