如何制作类的只读版本?

时间:2022-06-01 22:05:30

I have a class with various public properties which I allow users to edit through a property grid. For persistence this class is also serialized/deserialized to/from an XML file through DataContractSerializer.

我有一个具有各种公共属性的类,我允许用户通过属性网格进行编辑。对于持久性,此类还通过DataContractSerializer与XML文件进行序列化/反序列化。

Sometimes I want to user to be able to save (serialize) changes they've made to an instance of the class. Yet at other times I don't want to allow the user to save their changes, and should instead see all the properties in the property grid as read only. I don't want to allow users to make changes that they'll never be able to save later. Similar to how MS Word will allow users to open documents that are currently opened by someone else but only as read only.

有时我希望用户能够将他们所做的更改保存(序列化)到类的实例中。但在其他时候,我不想让用户保存他们的更改,而应该将属性网格中的所有属性都看作只读。我不想让用户进行以后永远无法保存的更改。类似于MS Word将允许用户打开当前由其他人打开但仅作为只读的文档。

My class has a boolean property that determines if the class should be read-only, but is it possible to use this property to somehow dynamically add a read-only attributes to the class properties at run-time? If not what is an alternative solution? Should I wrap my class in a read-only wrapper class?

我的类有一个布尔属性,用于确定该类是否应该是只读的,但是是否可以使用此属性以某种方式在运行时动态地向类属性添加只读属性?如果不是什么是替代解决方案?我应该将我的类包装在只读包装类中吗?

7 个解决方案

#1


16  

Immutability is an area where C# still has room to improve. While creating simple immutable types with readonly properties is possible, once you need more sophisticated control over when type are mutable you start running into obstacles.

不变性是C#仍有改进空间的领域。虽然创建具有只读属性的简单不可变类型是可能的,但是一旦您需要对类型可变的更复杂的控制,您就开始遇到障碍。

There are three choices that you have, depending on how strongly you need to "enforce" read-only behavior:

您有三种选择,具体取决于您需要“强制执行”只读行为的强度:

  1. Use a read-only flag in your type (like you're doing) and let the caller be responsible for not attempting to change properties on the type - if a write attempt is made, throw an exception.

    在您的类型中使用只读标志(就像您正在做的那样)并让调用者负责不尝试更改类型的属性 - 如果进行了写入尝试,则抛出异常。

  2. Create a read-only interface and have your type implement it. This way you can pass the type via that interface to code that should only perform reads.

    创建一个只读接口,让您的类型实现它。这样,您可以通过该接口将类型传递给只应执行读取的代码。

  3. Create a wrapper class that aggregates your type and only exposes read operations.

    创建一个聚合您的类型的包装类,只显示读取操作。

The first option is often the easiest, in that it can require less refactoring of existing code, but offers the least opportunity for the author of a type to inform consumers when an instance is immutable vs when it is not. This option also offers the least support from the compiler in detecting inappropriate use - and relegates error detection to runtime.

第一种选择通常是最简单的,因为它可以减少对现有代码的重构,但是为类型的作者提供了最少的机会,以便在实例不可变时通知消费者而不是实例。此选项还提供编译器在检测不当使用方面的最少支持 - 并将错误检测降级到运行时。

The second option is convenient, since implementing an interface is possible without much refactoring effort. Unfortunately, callers can still cast to the underlying type and attempt to write against it. Often, this option is combined with a read-only flag to ensure the immutability is not violated.

第二个选项很方便,因为实现一个接口是可能的,没有太多的重构努力。不幸的是,调用者仍然可以转换为基础类型并尝试对其进行编写。通常,此选项与只读标志组合以确保不违反不变性。

The third option is the strongest, as far as enforcement goes, but it can result in duplication of code and is more of a refactoring effort. Often, it's useful to combine option 2 and 3, to make the relationship between the read-only wrapper and the mutable type polymorphic.

就强制执行而言,第三种选择是最强的,但它可能导致代码重复,更多的是重构工作。通常,组合选项2和3非常有用,可以建立只读包装器和可变类型多态的关系。

Personally, I tend to perfer the third option when writing new code where I expect to need to enforce immutability. I like the fact that it's impossible to "cast-away" the immutable wrapper, and it often allows you to avoid writing messy if-read-only-throw-exception checks into every setter.

就个人而言,在编写需要强制执行不变性的新代码时,我倾向于使用第三种选择。我喜欢这样一个事实:不可能“抛弃”不可变包装器,它通常允许你避免在每个setter中编写凌乱的if-read-only-throw-exception检查。

#2


2  

Why not something like:

为什么不是这样的:

private int someValue;
public int SomeValue
{
    get
    {
         return someValue;
    }
    set
    {
         if(ReadOnly)
              throw new InvalidOperationException("Object is readonly");
         someValue= value;
    }

#3


2  

If you are creating a library, it is possible to define a public interface with a private/internal class. Any method which needs to return an instance of your read-only class to an external consumer should instead return an instance of the read-only interface instead. Now, down-casting to a concrete type is impossible since the type isn't publicly exposed.

如果要创建库,则可以使用私有/内部类定义公共接口。任何需要将只读类的实例返回给外部使用者的方法应该改为返回只读接口的实例。现在,由于类型没有公开暴露,因此不可能向下浇注到混凝土类型。

Utility Library

public interface IReadOnlyClass
{
    string SomeProperty { get; }
    int Foo();
}
public interface IMutableClass
{
    string SomeProperty { set; }
    void Foo( int arg );
}

Your Library

internal MyReadOnlyClass : IReadOnlyClass, IMutableClass
{
    public string SomeProperty { get; set; }
    public int Foo()
    {
        return 4; // chosen by fair dice roll
                  // guaranteed to be random
    }
    public void Foo( int arg )
    {
        this.SomeProperty = arg.ToString();
    }
}
public SomeClass
{
    private MyThing = new MyReadOnlyClass();

    public IReadOnlyClass GetThing 
    { 
        get 
        { 
            return MyThing as IReadOnlyClass;
        }
    }
    public IMutableClass GetATotallyDifferentThing
    {
        get
        {
            return MyThing as IMutableClass
        }
    }
}

Now, anyone who uses SomeClass will get back what looks like two different objects. Of course, they could use reflection to see the underlying types, which would tell them that this is really the same object with the same type. But the definition of that type is private in an external library. At this point, it is still technically possible to get at the definition, but it requires Heavy Wizardry to pull off.

现在,任何使用SomeClass的人都会看到两个不同的对象。当然,他们可以使用反射来查看底层类型,这会告诉他们这实际上是同一类型的相同对象。但是该类型的定义在外部库中是私有的。此时,技术上仍然可以达到定义,但它需要Heavy Wizardry才能完成。

Depending on your project, you could combine the above libraries into one. There is nothing preventing that; just don't include the above code in whatever DLL you want to restrict the permissions of.

根据您的项目,您可以将上述库合并为一个。什么都没有阻止;只是不要在你想限制权限的任何DLL中包含上面的代码。

Credit to XKCD for the comments.

感谢XKCD的评论。

#4


1  

I would use a wrapper class that keeps everything read-only. This is for scalability, reliability and general readability.

我会使用一个包装类来保持所有内容都是只读的。这是为了可扩展性,可靠性和一般可读性。

I do not foresee any other methods of doing this that will provide the above three mentioned benefits as well as something more. Definitely use a wrapper class here in my opinion.

我没有预见到这样做的任何其他方法将提供上述三个好处以及更多。在我看来,绝对使用包装类。

#5


0  

You can not get compile-time checks (like given with the keyword readonly) by changing a property to readonly at runtime. So there is no other way, as to check manually and throw an exception.

通过在运行时将属性更改为只读,您无法获得编译时检查(如使用关键字readonly给出的)。因此,没有其他方法可以手动检查并抛出异常。

But propably it is better to re-design access to the class. For example create a "writer class", which checks if the underling "data class" can currently be written or not.

但可取的是,重新设计对课程的访问权限会更好。例如,创建一个“编写器类”,它检查当前是否可以写入基础“数据类”。

#6


0  

You can use PostSharp to create OnFieldAccessAspect that will not pass new value to any field when _readOnly will be set to true. With aspect code repetition is gone and there will be no field forgotten.

您可以使用PostSharp创建OnFieldAccessAspect,当_readOnly设置为true时,它不会将新值传递给任何字段。随着方面代码重复的消失,将不会忘记任何字段。

#7


0  

Would something like this help:

这样的事情会有所帮助:

class Class1
{
    private bool _isReadOnly;

    private int _property1;
    public int Property1
    {
        get
        {
            return _property1;
        }
        set
        {
            if (_isReadOnly) 
              throw new Exception("At the moment this is ready only property.");
            _property1 = value;
        }
    }
}

You need to catch exceptions when setting properties.

设置属性时需要捕获异常。

I hope this is something you are looking for.

我希望这是你正在寻找的东西。

#1


16  

Immutability is an area where C# still has room to improve. While creating simple immutable types with readonly properties is possible, once you need more sophisticated control over when type are mutable you start running into obstacles.

不变性是C#仍有改进空间的领域。虽然创建具有只读属性的简单不可变类型是可能的,但是一旦您需要对类型可变的更复杂的控制,您就开始遇到障碍。

There are three choices that you have, depending on how strongly you need to "enforce" read-only behavior:

您有三种选择,具体取决于您需要“强制执行”只读行为的强度:

  1. Use a read-only flag in your type (like you're doing) and let the caller be responsible for not attempting to change properties on the type - if a write attempt is made, throw an exception.

    在您的类型中使用只读标志(就像您正在做的那样)并让调用者负责不尝试更改类型的属性 - 如果进行了写入尝试,则抛出异常。

  2. Create a read-only interface and have your type implement it. This way you can pass the type via that interface to code that should only perform reads.

    创建一个只读接口,让您的类型实现它。这样,您可以通过该接口将类型传递给只应执行读取的代码。

  3. Create a wrapper class that aggregates your type and only exposes read operations.

    创建一个聚合您的类型的包装类,只显示读取操作。

The first option is often the easiest, in that it can require less refactoring of existing code, but offers the least opportunity for the author of a type to inform consumers when an instance is immutable vs when it is not. This option also offers the least support from the compiler in detecting inappropriate use - and relegates error detection to runtime.

第一种选择通常是最简单的,因为它可以减少对现有代码的重构,但是为类型的作者提供了最少的机会,以便在实例不可变时通知消费者而不是实例。此选项还提供编译器在检测不当使用方面的最少支持 - 并将错误检测降级到运行时。

The second option is convenient, since implementing an interface is possible without much refactoring effort. Unfortunately, callers can still cast to the underlying type and attempt to write against it. Often, this option is combined with a read-only flag to ensure the immutability is not violated.

第二个选项很方便,因为实现一个接口是可能的,没有太多的重构努力。不幸的是,调用者仍然可以转换为基础类型并尝试对其进行编写。通常,此选项与只读标志组合以确保不违反不变性。

The third option is the strongest, as far as enforcement goes, but it can result in duplication of code and is more of a refactoring effort. Often, it's useful to combine option 2 and 3, to make the relationship between the read-only wrapper and the mutable type polymorphic.

就强制执行而言,第三种选择是最强的,但它可能导致代码重复,更多的是重构工作。通常,组合选项2和3非常有用,可以建立只读包装器和可变类型多态的关系。

Personally, I tend to perfer the third option when writing new code where I expect to need to enforce immutability. I like the fact that it's impossible to "cast-away" the immutable wrapper, and it often allows you to avoid writing messy if-read-only-throw-exception checks into every setter.

就个人而言,在编写需要强制执行不变性的新代码时,我倾向于使用第三种选择。我喜欢这样一个事实:不可能“抛弃”不可变包装器,它通常允许你避免在每个setter中编写凌乱的if-read-only-throw-exception检查。

#2


2  

Why not something like:

为什么不是这样的:

private int someValue;
public int SomeValue
{
    get
    {
         return someValue;
    }
    set
    {
         if(ReadOnly)
              throw new InvalidOperationException("Object is readonly");
         someValue= value;
    }

#3


2  

If you are creating a library, it is possible to define a public interface with a private/internal class. Any method which needs to return an instance of your read-only class to an external consumer should instead return an instance of the read-only interface instead. Now, down-casting to a concrete type is impossible since the type isn't publicly exposed.

如果要创建库,则可以使用私有/内部类定义公共接口。任何需要将只读类的实例返回给外部使用者的方法应该改为返回只读接口的实例。现在,由于类型没有公开暴露,因此不可能向下浇注到混凝土类型。

Utility Library

public interface IReadOnlyClass
{
    string SomeProperty { get; }
    int Foo();
}
public interface IMutableClass
{
    string SomeProperty { set; }
    void Foo( int arg );
}

Your Library

internal MyReadOnlyClass : IReadOnlyClass, IMutableClass
{
    public string SomeProperty { get; set; }
    public int Foo()
    {
        return 4; // chosen by fair dice roll
                  // guaranteed to be random
    }
    public void Foo( int arg )
    {
        this.SomeProperty = arg.ToString();
    }
}
public SomeClass
{
    private MyThing = new MyReadOnlyClass();

    public IReadOnlyClass GetThing 
    { 
        get 
        { 
            return MyThing as IReadOnlyClass;
        }
    }
    public IMutableClass GetATotallyDifferentThing
    {
        get
        {
            return MyThing as IMutableClass
        }
    }
}

Now, anyone who uses SomeClass will get back what looks like two different objects. Of course, they could use reflection to see the underlying types, which would tell them that this is really the same object with the same type. But the definition of that type is private in an external library. At this point, it is still technically possible to get at the definition, but it requires Heavy Wizardry to pull off.

现在,任何使用SomeClass的人都会看到两个不同的对象。当然,他们可以使用反射来查看底层类型,这会告诉他们这实际上是同一类型的相同对象。但是该类型的定义在外部库中是私有的。此时,技术上仍然可以达到定义,但它需要Heavy Wizardry才能完成。

Depending on your project, you could combine the above libraries into one. There is nothing preventing that; just don't include the above code in whatever DLL you want to restrict the permissions of.

根据您的项目,您可以将上述库合并为一个。什么都没有阻止;只是不要在你想限制权限的任何DLL中包含上面的代码。

Credit to XKCD for the comments.

感谢XKCD的评论。

#4


1  

I would use a wrapper class that keeps everything read-only. This is for scalability, reliability and general readability.

我会使用一个包装类来保持所有内容都是只读的。这是为了可扩展性,可靠性和一般可读性。

I do not foresee any other methods of doing this that will provide the above three mentioned benefits as well as something more. Definitely use a wrapper class here in my opinion.

我没有预见到这样做的任何其他方法将提供上述三个好处以及更多。在我看来,绝对使用包装类。

#5


0  

You can not get compile-time checks (like given with the keyword readonly) by changing a property to readonly at runtime. So there is no other way, as to check manually and throw an exception.

通过在运行时将属性更改为只读,您无法获得编译时检查(如使用关键字readonly给出的)。因此,没有其他方法可以手动检查并抛出异常。

But propably it is better to re-design access to the class. For example create a "writer class", which checks if the underling "data class" can currently be written or not.

但可取的是,重新设计对课程的访问权限会更好。例如,创建一个“编写器类”,它检查当前是否可以写入基础“数据类”。

#6


0  

You can use PostSharp to create OnFieldAccessAspect that will not pass new value to any field when _readOnly will be set to true. With aspect code repetition is gone and there will be no field forgotten.

您可以使用PostSharp创建OnFieldAccessAspect,当_readOnly设置为true时,它不会将新值传递给任何字段。随着方面代码重复的消失,将不会忘记任何字段。

#7


0  

Would something like this help:

这样的事情会有所帮助:

class Class1
{
    private bool _isReadOnly;

    private int _property1;
    public int Property1
    {
        get
        {
            return _property1;
        }
        set
        {
            if (_isReadOnly) 
              throw new Exception("At the moment this is ready only property.");
            _property1 = value;
        }
    }
}

You need to catch exceptions when setting properties.

设置属性时需要捕获异常。

I hope this is something you are looking for.

我希望这是你正在寻找的东西。