在C#中提供只读的类列表

时间:2011-06-07 11:33:45

标签: c# design-patterns

我有一组可用于操作基本数据块的自定义数据类型。例如:

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列表中获取一个类型(例如,通过选择组合框项目),然后直接使用该引用,而不是创建该类型的克隆并使用它们自己的引用。 / p>

如何将可用类型列表设为只读,以便它不允许对类型实例进行任何写入或更改,从而强制用户创建自己的克隆?

或者,有没有更好的方法来提供可用类型列表?

谢谢,安迪

6 个答案:

答案 0 :(得分:3)

使您的自定义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的新实例。

我希望这可以帮助您更好地理解不可变对象以及它们如何在您的特定情况下帮助您。

答案 1 :(得分:2)

创建类型列表而不是实例列表。 e.g。

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名称而不包含所有其他guff,例如MyTypeA

答案 2 :(得分:2)

您可能不应该在列表中存储类型的实例。相反,您可以存储类型。这些可用于创建实例:

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 的类型。

您可以考虑创建和属性,然后可以将其应用于MyTypeAMyTypeB等。然后,您可以使用反射构建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));

答案 3 :(得分:2)

集合不能“注入”类型修饰符到其成员中。您声明的集合是只读的。如果您希望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;

答案 4 :(得分:2)

为了解决您提到的问题,您可以在实例周围创建一个包装器,并让包装器提供您需要的功能。

例如:

    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。

答案 5 :(得分:1)

我不确定你想要什么,但是这样的事情应该可以吗?

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

这实际上是返回类型而不是这些类型的示例实例。因此,您可以确保您的班级用户只能创建新实例。

然后在调用代码中:

MyTypeA a = Activator.CreateInstance(DataTypes.AvailableTypes["MyTypeA"]);