2013-06-24 4 views
6

Для некоторых классов, в идеале, я хотел бы создать специальные именованные экземпляры, похожие на «null». Насколько я знаю, что это невозможно, так что вместо этого я создаю статические экземпляры класса, со статическим конструктором, подобно этому: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, относящийся к Waldo, и я небрежно не проверяю, не означает ли это обращение к Waldo, тогда я случайно сжимаю свойства Waldo.

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

Единственное решение, которое я знаю прямо сейчас, заключается в реализации набора атрибутов get & и проверки «if (this == Waldo) throw new ...» на каждом наборе. Хотя это работает, я предполагаю, что C# может сделать лучше, чем я, для его реализации. Если только я могу найти некоторый способ C#, чтобы сделать все свойства Waldo readonly (за исключением статического конструктора.)

+0

Не могли бы вы просто использовать аксессуар в своем классе Waldo и не использовать мутатор? – Brian

+0

Не прямой ответ на ваш вопрос, но возможное направление: http://msdn.microsoft.com/en-us/library/ms750509.aspx и http://stackoverflow.com/questions/263585/immutable-object- pattern-in-c-sharp-what-do-you-think – hatchet

+0

Вы можете определить интерфейс 'IPerson' и сделать' Waldo' класс, реализующий 'IPerson', а также' Person' реализует его.Затем вам нужно сделать «Waldo» неизменным, посмотрите здесь, чтобы узнать больше о том, как вы делаете класс неизменным: http://stackoverflow.com/questions/352471/how-do-create-an-immutable-class –

ответ

0

Благодаря всем вашим предложениям - вот решение. Мне нужно было сделать строку виртуальной, переопределить accessors в открытом производном классе и использовать флаг 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; 
    } 
} 
4

Сделать частный класс внутри Человека, который наследует Лицо, ImmutablePerson : Person.

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

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

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

+0

+1, потому что я собирался сказать то же самое. –

+0

К сожалению, это не работает. Вы не можете публично публиковать частную собственность, следовательно, сделать Waldo доступным публично. –

1

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

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

Это не совсем сработало - get & set accessors необходимо определить в базовом классе, чтобы «нормальный» экземпляр мог быть модифицируемым. И после того, как доступный элемент набора становится общедоступным в базовом классе, вы не можете затем изменить «частный» или «общедоступный» модификатор доступа производного доступа. –

0

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