How can I have different protection levels for a s

2019-09-11 08:55发布

问题:

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?

回答1:

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.



回答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.



回答3:

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);
}


回答4:

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.