如何制作类的只读版本?

发布于 2024-08-31 00:59:14 字数 327 浏览 2 评论 0原文

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

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

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

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.

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.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

你是我的挚爱i 2024-09-07 00:59:14

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

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

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

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

  3. 创建一个包装类来聚合您的类型并仅公开读取操作。

第一个选项通常是最简单的,因为它需要对现有代码进行较少的重构,但为作者提供的机会最少。类型来通知消费者实例何时是不可变的,何时不是。此选项还为编译器在检测不当使用方面提供的支持最少,并将错误检测移交给运行时。

第二种选择很方便,因为无需太多重构工作就可以实现接口。不幸的是,调用者仍然可以转换为底层类型并尝试对其进行写入。通常,此选项与只读标志结合使用,以确保不违反不变性。

就强制执行而言,第三个选项是最强的,但它可能会导致代码重复,并且更多的是重构工作。通常,结合选项 2 和选项 3 可以使只读包装器和可变类型之间的关系具有多态性。

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

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.

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 versus 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.

Personally, I tend to prefer 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.

月竹挽风 2024-09-07 00:59:14

如果您正在创建库,则可以使用私有/内部类定义公共接口。任何需要将只读类的实例返回给外部使用者的方法都应该返回只读接口的实例。接口。现在,向下转换为具体类型是不可能的,因为该类型没有公开暴露。

实用程序库

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

你的库

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

现在,任何使用SomeClass的人都将得到看起来像两个不同对象的东西。当然,他们可以使用反射来查看底层类型,这将告诉他们这实际上是具有相同类型的同一对象。但该类型的定义在外部库中是私有的。此时,从技术上来说仍然可以得到定义,但需要 Heavy魔法来实现。

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

感谢 XKCD 的评论。

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.

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.

Credit to XKCD for the comments.

起风了 2024-09-07 00:59:14

为什么不类似:

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

Why not something like:

private int someValue;
public int SomeValue
{
    get
    {
         return someValue;
    }
    set
    {
         if(ReadOnly)
              throw new InvalidOperationException("Object is readonly");
         someValue= value;
    }
零度° 2024-09-07 00:59:14

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

我预计没有任何其他方法可以提供上述三个好处以及更多的好处。我认为这里绝对使用包装类。

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.

楠木可依 2024-09-07 00:59:14

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

但可能最好重新设计对类的访问。例如,创建一个“编写器类”,它检查当前是否可以写入底层“数据类”。

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.

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.

还如梦归 2024-09-07 00:59:14

您可以使用 PostSharp 创建 OnFieldAccessAspect,当 _readOnly 设置为 true 时,它​​不会将新值传递给任何字段。有了方面代码重复就消失了,并且不会有任何字段被遗忘。

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.

初相遇 2024-09-07 00:59:14

像这样的东西会有所帮助吗:

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

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

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

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.

假装爱人 2024-09-07 00:59:14

我可能迟到了,但我遇到了这个问题,并认为我应该为其他人添加更新。

在 C# 10 中引入了记录。
它们使您可以更轻松地创建不可变的东西,并带来其他东西。

public record Person(string FirstName, string LastName);

来源:https://learn.microsoft。 com/en-us/dotnet/csharp/language-reference/builtin-types/record

在这里你可以查找 classrecord 之间的详细比较。

I'm probably late to the party, but I came across this question and thought I should add an update for others.

In C# 10 records got introduced.
They allow you to create immutable things more easily and also bring other things.

public record Person(string FirstName, string LastName);

source: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record

Here can u find a detailed comparison between class and record.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文