0

I had a class with 6 properties which were basically 3 pairs of properties, each pair containing data about one thing. All of these properties were public, but setting them was protected. It is shown in the code below:

public class MyClass
{
    public Data1Type Item1Data1 { get; protected set; }
    public Data2Type Item1Data2 { get; protected set; }
    public Data1Type Item2Data1 { get; protected set; }
    public Data2Type Item2Data2 { get; protected set; }
    public Data1Type Item3Data1 { get; protected set; }
    public Data2Type Item3Data2 { get; protected set; }
}

Because each pair of properties was basically treated as one item, I decided to make a struct which looks like this:

struct Item
{
    Data1Type Data1;
    Data2Type Data2;
}

So I replaced each pair of properties with one Item struct.

The problem I'm facing now is that I can't find a way to have the same protection level I had before, with the 3 pairs of properties. I want everything outside MyClass to be able to get the properties inside the Item structs, but only MyClass and classes derived from it to be able to change the properties inside the Item structs.

How can I do such a thing? Is it even possible?

Cokegod
  • 8,256
  • 10
  • 29
  • 47

4 Answers4

3

You can make your struct read-only to retain control over the values. This is a pattern used in parts of .NET:

struct MyStruct
{
    public readonly int Field1;
    public readonly int Field2;

    public MyStruct(int i, int j)
    {
        Field1 = i;
        Field2 = j;
    }
}

Then you can create your properties the same way you did before, knowing that the values inside the struct will remain unchanged unless you go through the property setter.

Servy
  • 202,030
  • 26
  • 332
  • 449
Sten Petrov
  • 10,943
  • 1
  • 41
  • 61
  • 2
    Since you're exposing them publicly, `Field1` and `Field2` should probably be properties, not straight fields. – Servy Feb 01 '13 at 15:58
  • @Servy in struct it won't make much of a difference and it makes the code verbose. How do you suggest we declare the properties? With a read-only backing field and just a getter? Private setter? If this were a class I'd agree – Sten Petrov Feb 01 '13 at 16:03
  • 3
    `public int Field2 {get;private set;}` It's a grand total of 16 extra characters, most of which will be added by Intellisense. It's a trivial amount of extra work for a number of subtle benefits. If you needed to write it all out, I could see it not being work the effort, but once auto implemented properties were added there was no excuse left to have public fields just because it's "easier". – Servy Feb 01 '13 at 16:05
  • @Servy now go ahead and try that in a compiler. You get two compiler errors from this code. (3609 and 3610). You still need a backing field, hence the verbosity I'm trying to avoid. – Sten Petrov Feb 01 '13 at 16:10
  • 2
    You'll also need to add `:this()` to the end of the constructor to initialize the backing fields, but adding that call is generally habit for most struct constructors. – Servy Feb 01 '13 at 16:12
  • Thanks, although I did use Servy's suggestion and put `{ get; private set; }` instead of `readonly`. – Cokegod Feb 01 '13 at 23:56
2

Simply create properties of your struct type with protected setter:

public class MyClass
{
    public Item Item1 { get; protected set; }
    public Item Item2 { get; protected set; }
    public Item Item3 { get; protected set; }
}

Struct is a value type, so client cannot change Data1 or Data2 property of item from MyClass (client will have copy of item). And with protected setter only MyClass and its inheritors can set new value for item. If you want item's data to be updated for some item, then use class instead of struct.

Sergey Berezovskiy
  • 232,247
  • 41
  • 429
  • 459
0

You should probably read more about struct - it's meant to be immutable, which means that you shouldn't be able to change an Item's properties, whether from MyClass or from anywhere else.

If you look at Stan Petrovs answer, you can see how structs should be made.

Your MyClass should then have something like:

protected void SetItem1Data1(DataType1 newValue) 
{
    this.Item1 = new Item(newValue, this.Item1.Data2);
}
Community
  • 1
  • 1
kͩeͣmͮpͥ ͩ
  • 7,783
  • 26
  • 40
  • The fact that structure types can be mutable is no accident; they're not "meant" to be immutable. Some of the people behind .net seem to have viewed structures as lightweight classes, and regarded any deviation from class behavior as a "defect", but others were clearly aware that piecewise-mutable value-types are uniquely useful in their own right *because* they don't behave like class types. In fact, both groups were right with regard to some usage cases: structs which try to behave like lightweight class objects should behave like immutable lightweight class objects, but... – supercat Feb 02 '13 at 17:40
  • ...there are other usage cases where it's much more helpful to have a struct that behaves like a bunch of variables stuck together with duct tape. The design of .net might have been cleaner if structs of the latter style didn't descend from `Object`, but if each such type `T` could have a default (and overridable) implicit conversion to and explicit conversion from a `T[1]` or something similar which did derive from `Object`. That's almost how things work now, except that the type system considers boxed and unboxed items to be the same type. – supercat Feb 02 '13 at 17:50
0

There are only two ways in which a construct like:

someThing.someMember.nestedMember = someValue;

can be legal: either someMember return a reference to a class instance, in which case someThing's involvement will cease as soon as the reference is returned, or else someThing.someMember must be a field of class someThing. It used to be legal in C# even if someMember returned a struct, to allow for the possibility that someMember might do something like:

public class Thing { ...
    private nestedType member_nestedMember;
    public struct MemberProxy {
        Thing _owner;
        internal MemberProxy(Thing owner) {_owner = owner;}
        nestedType nestedMember {
            get {return _owner.member_nestedMember;}
            set {_owner.member_nestedMember = value; }
        }
    }
    public MemberProxy someMember {get {return new MemberProxy(this);} }
}

In this scenario, MemberProxy doesn't actually hold the data for nestedMember, but instead holds a reference to the actual place it's contained. Thus, even if an instance of MemberProxy is not writable, an attempt to set its nestedMember property would work. Unfortunately, even though there are a few cases where invoking a property setter on a read-only struct value would be helpful (ArraySegment would provide another such example) there is as yet no attribute defined which would tell the compiler when such a thing should or should not be permissible.

Returning to your present scenario, I would suggest if there's any substantial likelihood that your type or a derived type may wish to alter one piece of an item without altering both, your best bet may be to declare an open-field structure of your composite-item type and include within your class protected fields of that type, along with public properties which return them. Doing that would allow your derived classes to say:

item1.Data1 = whatever;

while outside code would have to use the Item1 property and even if Data1 is an exposed writable field of type Item, an attempt to say:

item1.Data1 = whatever;

would not compile [note that even older compilers which would accept such a statement if Data1 were a read/write property, on the theory that it might be useful, would correctly reject it when Data1 is a field]. If you're determined not to expose a public mutable structure, you could always do something like:

public struct Item
{
   privateItem Value;
   public Type1 Data1 {get {return Value.Data1; }}
   public Type1 Data2 {get {return Value.Data2; }}
   Item(privateItem src)
   {
     Value = src;
   }
 }

I personally wouldn't really see any value in adding an extra layer of wrapping, but it might help appease the "public mutable structs are evil" crowd.

supercat
  • 77,689
  • 9
  • 166
  • 211