【问题标题】:c# readonly objectc# 只读对象
【发布时间】:2010-08-23 17:42:13
【问题描述】:

有没有办法返回一个对象的只读实例?

public class Person
{
    public String FirstName { get; set; }
    public String LastName { get; set; }
}

public class SomeClass
{
    public SomeClass(Person manager)
    {
        if (manager == null)
            throw new ArgumentNullException("manager");

        _manager = manager;
    }

    private readonly Person _manager;
    public Person Manager
    {
        get { return _manager; } //How do I make it readonly period!
    }
}

是通过返回 Clone() 来做到这一点的唯一方法,以便对 Clone 而不是对原始版本进行任何更改?我知道对于数组,有一个函数可以将数组作为只读返回。哦,我知道这是一个引用类型……我还想知道是否有一些隐藏的 C# 功能可以锁定写作部分。

我试图想出一个通用的 ReadOnly 包装类,但不知道如何在不进行一些昂贵的反射等的情况下将属性设为只读。

哦,我真的想避免创建所有只读属性的类的第二个版本。到时候我还不如把克隆还给我。

【问题讨论】:

  • 基于 cmets:是否有任何已知的快速高效的“ReadOnly”通用包装类?我尝试制作的必须使用反射,如果可以避免的话,这并不是我真正想做的。

标签: c# object readonly reference-type


【解决方案1】:

为了避免创建额外的类,您可以将其实现为仅具有只读属性的接口 IPerson。

public interface IPerson
{
    string FirstName { get; }
    string LastName { get; }
}
public class Person:IPerson
{
    public String FirstName { get; set; }
    public String LastName { get; set; }
}

public class SomeClass
{
public SomeClass(Person manager)
{
    if (manager == null)
        throw new ArgumentNullException("manager");

    _manager = manager;
}

private readonly Person _manager;
public IPerson Manager
{
    get { return _manager; } //How do I make it readonly period!
}
}

【讨论】:

  • 使用接口而不是具体类型有很好的理由(如果Person 有任何行为,我也会推荐这个)......但是与创建一个上课?
  • 我也喜欢这个,但是他们将它转换回 Person 类不是很容易吗? Person iCanChangeProperties = (Person)SomeClass.Manager; 并且 Cast 并不能防止他们使用该引用来更改属性......我可能只需要坚持克隆。不过,ReadOnly 接口会很好,所以反射会告诉他们他们不能做 setter 属性。
  • @myermian,你是对的。尽管对用户来说投射它是一种糟糕的做法。让 Person internal 可以阻止这种情况(但会阻止他们构建新的)。即使标记为内部,使用反射,他们也可以访问设置器。此外,传回克隆不会阻止用户反映 SomeClass 并直接获取 _manager。你不能阻止人们朝自己的脚开枪。当有人开始反对您公开的 API 时,他们应该承担由此带来的任何后果。
  • @jeff 我相信好处可能来自维护人员。如果 IPerson 接口发生变化,编译器将强制更新 Person 以匹配它。此外,如果使用 ReadOnlyPerson 类,则需要创建某种 Person->ReadOnlyPerson 映射。
  • @statenjason,我想你是对的。您只能做到这一点,以保护用户免于“射中自己的脚”。
【解决方案2】:

您可以将 Person 类转换为不可变对象,如下所示..

public class Person 
{ 
    public Person( string firstName, string lastName )
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public String FirstName { get; private set; } 
    public String LastName { get; private set; } 

} 

【讨论】:

    【解决方案3】:

    Castle.DynamicProxy 的帮助下,您可以在某些条件下冻结对象(使其不可变)。阅读此博客post 了解详情。

    【讨论】:

      【解决方案4】:

      没有这样的功能 - 你已经涵盖了你的选择。

      要么克隆它,要么创建一个只读的Person 类型。通常首选后一种方法,因为语义更清晰:调用者很明显他们不应该(也不能)修改实例。

      【讨论】:

      • 这种方法在 C# 标准库中也很常见。请参阅 String/StringBuilderUri/UriBuilder 之类的内容。只有当只读用法更多时才有意义,这两个就是这种情况。
      【解决方案5】:

      这是另一个示例,基于 List.AsReadOnly 在 .NET Framework 2.0 中的实现方式。在适当的方法中使用布尔值 (IsReadOnly) 来防止更新:

      public class Person
      {
          private string _firstName;
          public string FirstName
          {
              get { return _firstName; }
              set
              {
                  if (!IsReadOnly) _firstName = value;
                  else throw new AccessViolationException("Object is read-only.");
              }
          }
      
          private string _lastName;
          public string LastName
          {
              get { return _lastName; }
              set
              {
                  if (!IsReadOnly) _lastName = value;
                  else throw new AccessViolationException("Object is read-only.");
              }
          }
      
          internal virtual bool IsReadOnly { get { return false; } }
      
          public ReadOnlyPerson AsReadOnly()
          {
              return new ReadOnlyPerson(this);
          }
      
          public class ReadOnlyPerson : Person
          {
              private Person _person;
              internal override bool IsReadOnly { get { return true; } }
      
              internal ReadOnlyPerson(Person person) // Contructor
              {
                  this._person = person;
              }
          }
      }
      

      测试它:

      static void Main(string[] args)
      {
          Person p1 = new Person();
          p1.FirstName = "Joe";
          p1.LastName = "Bloe";
          Console.WriteLine("First = {0} Last = {1}", p.FirstName, p.LastName);
      
          var p2 = p1.AsReadOnly();
          p2.FirstName = "Josephine"; // AccessViolationException
      }
      

      【讨论】:

        【解决方案6】:

        没有办法让你的对象的所有属性在类外部只读。在上面的示例中,您不能将 _manager 属性设置为只读,除非您将 Person 类中的属性更改为只读。

        您可以将 Person 类的属性设置器设置为内部,这意味着只有与 Person 位于同一程序集中的类才能更改属性。

        或者,如果您将属性的设置器设为私有,则只有 Person 中的代码才能更改属性的值。

        【讨论】:

          【解决方案7】:

          没有。您正在寻找类似 C++ 风格的 const-ness,而对于 various reasons C# 没有。

          但是,匿名类型是真正不可变的。

          【讨论】:

            【解决方案8】:

            匿名对象是只读的。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-09-03
              • 2017-06-27
              • 2016-02-12
              • 2021-07-19
              • 1970-01-01
              • 1970-01-01
              • 2019-04-25
              • 1970-01-01
              相关资源
              最近更新 更多