Предоставление списка классов только для чтения в C # - PullRequest
2 голосов
/ 07 июня 2011

У меня есть набор пользовательских типов данных, которые можно использовать для управления основными блоками данных. Например:

MyTypeA Foo = new MyTypeA();
Foo.ParseString(InputString);
if (Foo.Value > 4) return;

Некоторые из этих типов определяют свойства только для чтения, которые описывают аспекты типов (например, имя, размер бита и т. Д.).

В своей пользовательской среде я хочу иметь возможность предоставлять эти типы пользователю для использования в своих приложениях, но я также хочу предоставить пользователю список доступных типов, которые они могут легко связать с выпадающим списком. Мой текущий подход:

public static class DataTypes
{
    static ReadOnlyCollection<MyDataType> AvailableTypes;

    static DataTypes()
    {
        List<MyDataType> Types = new List<MyDataType>();
        Types.Add(new MyTypeA());
        Types.Add(new MyTypeB());
        AvailableTypes = new ReadOnlyCollection<MyDataType>(Types);
    }
}

Что меня беспокоит по этому поводу, так это то, что пользователь может получить тип из списка AvailableTypes (выбрав, например, элемент комбинированного списка), а затем напрямую использовать эту ссылку, а не создавать клон типа и использовать собственную ссылку.

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

В качестве альтернативы, есть ли лучший способ предоставления списка доступных типов?

Спасибо, Энди

Ответы [ 6 ]

3 голосов
/ 07 июня 2011

Сделайте свой пользовательский класс Type неизменным, как и System.Type, и вам не о чем беспокоиться. Конечный пользователь может получить все необходимые ему данные, но не может каким-либо образом изменить объект.

РЕДАКТИРОВАТЬ: Пример неизменяемого класса

Например, возьмем следующий класс:

public class ImmutablePerson
{
    private readonly string name; //readonly ensures the field can only be set in the object's constructor(s).
    private readonly int age;

    public ImmutablePerson(string name, int age)
    {
         this.name = name;
         this.age = age;
    }

    public int Age { get { return this.age; } } //no setter
    public string Name { get { return this.name; } }

    public ImmutablePerson GrowUp(int years)
    { 
         return new ImmutablePerson(this.name, this.age + years); //does not modify object state, it returns a new object with the new state.
    }
}

ImmutablePerson является неизменным классом. После создания потребитель никак не может его изменить. Обратите внимание, что метод GrowUp(int years) вообще не изменяет состояние объекта, он просто возвращает новый экземпляр ImmutablePerson с новыми значениями.

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

2 голосов
/ 07 июня 2011

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

Например:

    public class TypeDescriptor
    {
        private MyDataType _dataType;

        public TypeDescriptor(MyDataType dataType)
        {
            _dataType = dataType;
        }

        public override string ToString()
        {
            return _dataType.ToString();
        }
    }

Ваш класс будет выглядеть примерно так:

    public static class DataTypes
    {
        public static ReadOnlyCollection<TypeDescriptor> AvailableTypes;

        static DataTypes()
        {
            List<TypeDescriptor> Types = new List<TypeDescriptor>();
            Types.Add(new TypeDescriptor(new MyTypeA()));
            Types.Add(new TypeDescriptor(new MyTypeB()));
            AvailableTypes = new ReadOnlyCollection<TypeDescriptor>(Types);
        }
    }

Привязка к списку и использование ToString () теперь приведет к вызову типов данных ToString.

2 голосов
/ 07 июня 2011

Коллекция не может "вводить" модификаторы типов в свои члены. Объявленная вами коллекция доступна только для чтения. Если вы хотите, чтобы MyDataType был доступен только для чтения, вы должны заявить об этом.

Что-то вроде:

РЕДАКТИРОВАТЬ расширенный класс, чтобы иметь метод синтаксического анализа

public class MyDataType
{
    private MyDataType()
    {
        ...
    }

    internal static MyDataType Parse(string someString)
    {
        MyDataType newOne = new MyDataType();
        newOne.Value = ... //int.Parse(someString); ?
    }

    public int Value { get; private set; }
}

Если коллекция остается универсальной, ограничений на чтение нет.

Вы бы использовали это так, следуя вашему примеру.

MyTypeA foo = MyTypeA.Parse(inputString);
if (foo.Value > 4) return;
2 голосов
/ 07 июня 2011

Вы, вероятно, не должны хранить экземпляры ваших типов в списке.Вместо этого вы можете хранить типы.Они могут использоваться для создания экземпляров:

public static class DataTypes
{
    static ReadOnlyCollection<Type> AvailableTypes;

    static DataTypes()
    {
        List<Type> Types = new List<Type>();
        Types.Add(typeof(MyTypeA));
        Types.Add(typeof(MyTypeB));
        AvailableTypes = new ReadOnlyCollection<MyDataType>(Type);
    }
}

Вы можете использовать Activator.CreateInstance для создания конкретного экземпляра:

Object myType = Activator.CreateInstance(AvailableTypes[0]);

Если ваши типы не имеют общий базовый тип, вы не можете уменьшитьрезультат и Object не так полезны.

Также использование термина type в вашем коде делает мой пример немного запутанным, так как я предлагаю вам хранить типы чего-то под названием type .

Вы можете подумать о создании и атрибуте, который затем можете применить к MyTypeA, MyTypeB и т. Д. Затем вы можете построить AvailableTypes с использованием отражения, и список всегда будетбыть в курсе своего кода.Например, если вы добавите MyTypeC и используете атрибут, он будет автоматически добавлен в список.

Вы также можете добавить свойство отображаемой строки к атрибуту и ​​использовать его для отображения в поле со списком.Если вы хотите сделать это, вы должны сохранить небольшой объект, объединяющий тип и отображаемую строку в AvailableTypes.

Вот пример.Использование общих слов, таких как type и data , может сбить с толку, поэтому для выбора случайного имени я просто использую foo .Очевидно, вы должны использовать более описательное имя.

[AttributeUsage(AttributeTargets.Class, Inherited = false)]
sealed class FooAttribute : Attribute {

  public FooAttribute(String displayName) {
    DisplayName = displayName;
  }

  public String DisplayName { get; private set; }

}

Вы можете украсить свои классы, используя этот атрибут:

[Foo("Type A")]
class MyTypeA { ... }

[Foo("Type B")]
class MyTypeB { ... }

Для комбинированного списка вам нужен список фабричных объектов с хорошим ToString реализация (этот класс можно улучшить, добавив некоторую обработку ошибок, которую я оставил для экономии места):

class FooFactory {

  readonly Type type;

  public FooFactory(Type type) {
    this.type = type;
    DisplayName = ((FooAttribute) Attribute.GetCustomAttribute(
      type,
      typeof(FooAttribute))
    ).DisplayName;
  }

  public String DisplayName { get; private set; }

  public Object CreateFoo() {
    return Activator.CreateInstance(this.type);
  }

  public override String ToString() {
    return DisplayName;
  }

}

Возвращение Object из CreateFoo не очень полезно, но этоотдельная проблема.

Вы можете создать этот список во время выполнения:

var factories = Assembly
  .GetExecutingAssembly()
  .GetTypes()
  .Where(t => Attribute.IsDefined(t, typeof(FooAttribute)))
  .Select(t => new FooFactory(t));
2 голосов
/ 07 июня 2011

Создайте список типов, а не список экземпляров.например,

List<Type> Types = new List<Type>();
Types.Add(typeof(MyTypeA));
Types.Add(typeof(MyTypeB()));
etc.

Чтобы ответить на комментарий о привязке к выпадающему списку:

MyDropDown.Datasource = Type.Select(t => t.Name);
MyDropDown.DataBind();

Это не будет использовать пользовательское свойство ваших классов, но даст вам простое имя calssбез всякой другой болтовни, например, MyTypeA

1 голос
/ 07 июня 2011

Я не совсем уверен, что вы хотите, но должно ли что-то вроде этого быть в порядке?

 public static class DataTypes
 {
      static Dictionary<string,Type> AvailableTypes
      = new Dictionary<string,Type>()
      {
           { "MyTypeA", MyTypeA },
           { "MyTypeB", MyTypeB },
           ...
      };
 }

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

Затем в коде вызова:

MyTypeA a = Activator.CreateInstance(DataTypes.AvailableTypes["MyTypeA"]);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...