【问题标题】:C# How to create special instances of a class?C#如何创建一个类的特殊实例?
【发布时间】:2013-06-24 23:03:38
【问题描述】:

对于某些类,理想情况下,我想创建特殊的命名实例,类似于“null”。据我所知,这是不可能的,所以我使用静态构造函数创建类的静态实例,类似于:

public class Person
{
    public static Person Waldo;  // a special well-known instance of Person
    public string name;
    static Person()  // static constructor
    {
        Waldo = new Person("Waldo");
    }
    public Person(string name)
    {
        this.name = name;
    }
}

如您所见,Person.Waldo 是我创建的 Person 类的一个特殊实例,因为在我的程序中,有很多其他类可能想要引用这个特殊的知名实例。

实现这种方式的缺点是我不知道有什么方法可以使 Person.Waldo 的所有属性不可变,而“普通” Person 实例的所有属性都应该是可变的。每当我不小心有一个 Person 对象引用了 Waldo,并且我不小心没有检查它是否引用了 Waldo,然后我不小心破坏了 Waldo 的属性。

有没有更好的方法,甚至是一些额外的替代方法来定义一个类的特殊知名实例?

我现在知道的唯一解决方案是实现 get & set 访问器,并在每个集合上检查“if (this == Waldo) throw new ...”。虽然这可行,但我认为 C# 在实现它方面比我做得更好。如果我能找到一些 C# 方法来使 Waldo 的所有属性只读(静态构造函数期间除外)。

【问题讨论】:

  • 你不能只在你的 Waldo 类中使用访问器而不使用 mutator 吗?
  • 不是直接回答您的问题,而是可能的方向:msdn.microsoft.com/en-us/library/ms750509.aspxstackoverflow.com/questions/263585/…
  • 您可以定义一个接口IPerson 并使Waldo 成为实现IPerson 的类,Person 也实现它。然后你需要使 Waldo 不可变,查看这里了解如何使类不可变的更多信息:stackoverflow.com/questions/352471/…
  • 有趣的问题。以前从来没有这样做过,但如果你想让它真正不可变,我认为你将 Person 的方法实现为虚拟的,然后创建一个继承自 Person 的 Waldo 类。构造函数设置所有你重写的属性,只有获取方法,没有设置,然后你重写方法什么都不做,或者只返回值,不修改它们。
  • @WojciechKulik - 这发生在 .Net 中。例如,在 System.Drawing 中,Pen 类有一个称为不可变的私有字段。它有一个内部构造函数,它接受一个允许创建者指定实例是否不可变的值。这用于创建和分发“特殊” Pen 对象。这些特殊的 Pen 对象由 Pens 类公开。在这种情况下,Microsoft 使用的模式类似于我上面链接的 Frozen 类。对象知道它是可变的还是不可变的,并且任何会改变状态的属性或方法首先检查以确保实例是可变的。

标签: c# class instance named


【解决方案1】:

在继承 Person 的 Person 内部创建一个私有类 ImmutablePerson : Person

锁定所有属性设置器:例如用 NotImplementedException 覆盖它们。

你的静态 Person 初始化变成这样: public static readonly Person Waldo = new ImmutablePerson("Waldo");

静态构造函数也可以被移除。

【讨论】:

  • 不幸的是,这不起作用。您不能公开提供私有财产,因此也不能公开提供 Waldo。
【解决方案2】:

也许你可以有以下层次结构:

class Person
{
    protected string _name;
    public virtual string Name{
        get{
            return _name;
        }
    }
}

class EditablePerson:Person
{
    public new string Name{
        get{
            return _name;
        }
        set{
            _name=value;
        }
    }
    public Person AsPerson()
    {
        //either return this (and simply constrain by interface)
        //or create an immutable copy
    }
}

【讨论】:

  • 这不太奏效 - 需要在基类中定义 get 和 set 访问器,以便修改“正常”实例。一旦 set 访问器在基类中公开,您就不能再更改派生访问器的“私有”或“公共”访问修饰符。
【解决方案3】:

在我看来,最好的办法是始终在 Waldo 上返回一个新实例。这样原始的沃尔多就永远不会改变。但这会阻止您使用引用相等进行比较,因此您必须重写 Equals 和 GetHashCode。

【讨论】:

    【解决方案4】:

    感谢您的所有建议 - 这是解决方案。我需要将字符串设为虚拟,覆盖公共派生类中的访问器,并使用 bool 标志来允许修改。

    需要注意的是,“name”是一个引用类型,尽管我已经阻止更改“name”所指的内容,但如果它不是字符串,例如包含自修改的类方法,尽管阻止了对对象引用的修改,仍然可以修改对象的内容。

    class Program
    {
        static void Main(string[] args)
        {
            Person myPerson = new Person("Wenda");
            System.Console.WriteLine("myPerson is " + myPerson.name);       // Prints "myPerson is Wenda"
    
            if (myPerson == Person.Waldo)
                System.Console.WriteLine("Found Waldo (first attempt)");    // doesn't happen
            else
                System.Console.WriteLine("Still trying to find Waldo...");  // Prints "Still trying to find Waldo..."
    
            myPerson.name = "Bozo";
            System.Console.WriteLine("myPerson is now " + myPerson.name);   // Prints "myPerson is now Bozo"
    
            myPerson = Person.Waldo;
    
            if (myPerson == Person.Waldo)
                System.Console.WriteLine("Found Waldo (second attempt)");   // Prints "Found Waldo (second attempt)"
    
            System.Console.WriteLine("myPerson is " + myPerson.name);       // Prints "myPerson is Waldo"
            System.Console.WriteLine("Now changing to The Joker...");       // Prints "Now changing to The Joker"
            try
            {
                myPerson.name = "The Joker";                                // throws ImmutablePersonModificationAttemptException
            }
            catch (ImmutablePersonModificationAttemptException)
            {
                System.Console.WriteLine("Failed to change");               // Prints "Failed to change"
            }
            System.Console.WriteLine("myPerson is now " + myPerson.name);   // Prints "myPerson is now Waldo"
    
            Thread.Sleep(int.MaxValue); // keep the console alive long enough for me to see the result.
        }
    }
    public class Person
    {
        public static readonly ImmutablePerson Waldo = new ImmutablePerson("Waldo");
        public virtual string name { get; set; }
        public Person() // empty base constructor required by ImmutablePerson(string) constructor
        { }
        public Person(string name)
        {
            this.name = name;
        }
    }
    public class ImmutablePersonModificationAttemptException : Exception
    { }
    public class ImmutablePerson : Person
    {
        private bool allowMutation;
        protected string _name;
        public override string name
        {
            get
            {
                return _name;
            }
            set
            {
                if (allowMutation)
                    _name = value;
                else
                    throw new ImmutablePersonModificationAttemptException();
            }
        }
        public ImmutablePerson(string name)
            : base()
        {
            allowMutation = true;
            this.name = name;
            allowMutation = false;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2012-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多