C# Как создать особые экземпляры класса?

Для некоторых классов в идеале я хотел бы создать специальные именованные экземпляры, похожие на "ноль". Насколько я знаю, это невозможно, поэтому вместо этого я создаю статические экземпляры класса со статическим конструктором, похожим на этот:

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, ссылающийся на Уолдо, и я небрежно не проверяю, ссылается ли он на Уолдо, я случайно удаляю свойства Уолдо.

Есть ли лучший способ или даже несколько дополнительных альтернативных способов определения специальных хорошо известных экземпляров класса?

Единственное решение, которое я знаю прямо сейчас, - это реализовать методы доступа get & set и проверять, "если ( this == Waldo) бросать новый..." в каждом наборе. Хотя это работает, я предполагаю, что C# может лучше, чем я, реализовать его. Если бы я только мог найти какой-нибудь C# способ сделать все свойства Уолдо доступными только для чтения (кроме как в статическом конструкторе.)

4 ответа

Решение

Спасибо всем вашим предложениям - вот решение. Мне нужно было сделать строку виртуальной, переопределить методы доступа в общедоступном производном классе и использовать флаг bool для разрешения изменения.

Важно отметить, что "имя" является ссылочным типом, и хотя я не позволил изменить то, на что ссылается "имя", если бы оно было чем-то отличным от строки, например, класса, который содержит метод самоизменения, он все еще можно было бы изменить содержимое объекта, несмотря на то, что предотвратило изменение ссылки на объект.

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

Сделайте закрытый класс внутри Person, который наследует Person, ImmutablePerson : Person,

Сделайте все установщики свойств заблокированными: переопределите их, например, с помощью NotImplementedException.

Ваша статическая инициализация Person становится такой:public static readonly Person Waldo = new ImmutablePerson("Waldo");

И статический конструктор тоже может быть удален.

Может быть, вы могли бы иметь следующую иерархию:

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

На мой взгляд, лучше всего всегда возвращать новый экземпляр на Уолдо. Таким образом, оригинальный Уолдо никогда не изменится. Но это не позволит вам использовать равенство ссылок для сравнения, поэтому вам придется переопределить Equals и GetHashCode.

Другие вопросы по тегам