场景
枚举,其实就是一种特殊的整数,只不过只能取一系列特定的值。通过 enum Type : short 这样的语法,我们可以指定枚举在底层究竟使用哪种整数。
然而,有的时候我们希望自定义类型,其实例有着各种我们所需要的成员;但同时我们又希望这个类型只有有限个实例,用户只能从其中取一个使用。
比如,Anders Liu有一个系统,能够处理Word、Html和Text格式的文档,现在我希望定义一个DocumentType类型,表示所有支持的文档类型;但对于每一种类型,Anders Liu又偏偏希望它具有诸如Processor(处理这种类型文档的程序)和Extension(这种类型的文档的扩展名)等属性。
此时就出现了矛盾。如果使用枚举,必然无法实现这些实例属性。但是,如果自己编写一个类呢?
在这篇文章中,Anders Liu将带你一起实现一个用起来像枚举,但实际上是自定义类型的类。
我要的对象,它不是整数
首先,毕竟我们所需要的是对象,而不是枚举所提供的简单整数。所以,先定义好再说:
{
/**//// <summary>
/// Indicates the surpported document types.
/// You cannot inherits from this class.
/// </summary>
/// <remarks>
/// This class looks very like an enum.
/// </remarks>
public sealed class DocumentType
{
instanse members#region instanse members
string _name;
string _processor;
string _extension;
/**//// <summary>
/// Initialize a <see cref="DocumentType"/> instance.
/// This constructor is private, so that user cannot construct it from external.
/// </summary>
/// <param name="name">Name of the document type.</param>
/// <param name="processor">Processor of the document.</param>
/// <param name="extension">Extension of the document.</param>
private DocumentType(string name, string processor, string extension)
{
_name = name;
_processor = processor;
_extension = extension;
}
/**//// <summary>
/// Name of the document type.
/// </summary>
public string Name
{
get
{
return _name;
}
}
/**//// <summary>
/// Processor of the document.
/// </summary>
public string Processor
{
get
{
return _processor;
}
}
/**//// <summary>
/// Extension of the document.
/// </summary>
public string Extension
{
get
{
return _extension;
}
}
#endregion
}
}
在这里,要注意的是,这个DocumentType类型的构造器被定义成了private,因为毕竟我们所提供的只是有限个对象,所以不希望客户代码创建它的实例。
这个类型非常简单,如果构造器是public的,我想它就已经完成了。
有限个实例,我给你准备好
既然我们只能支持已知的几种类型,不如先准备好,放在一个静态的集合中。
用哪种集合类型最好呢?Anders Liu偏爱Dictionary。因为Dictionary在检索成员时的时间复杂度是O(1)!
好,继续向这个Document类中添加成员:
static Dictionary<string, DocumentType> _allTypes;
// Indicates the documents' type name.
const string WordTypeName = "Word";
const string HtmlTypeName = "Html";
const string TextTypeName = "Text";
// Indicates which application can be used to open the document.
const string WordProcessor = "winword.exe";
const string HtmlProcessor = "iexplorer.exe";
const string TextProcessor = "notepad.exe";
// Indicates the file extension of each document type.
const string WordExtension = ".doc";
const string HtmlExtension = ".html";
const string TextExtension = ".txt";
/**//// <summary>
/// Static constructor. Initializes all supported document types.
/// </summary>
static DocumentType()
{
_allTypes = new Dictionary<string, DocumentType>();
_allTypes.Add(WordTypeName, new DocumentType(WordTypeName, WordProcessor, WordExtension));
_allTypes.Add(HtmlTypeName, new DocumentType(HtmlTypeName, HtmlProcessor, HtmlExtension));
_allTypes.Add(TextTypeName, new DocumentType(TextTypeName, TextProcessor, TextExtension));
}
#endregion
现在,所有支持的文档类型实例就都位于这个_allTypes中了。
为了让程序漂亮一些,所有用到的字符串都使用常量代替了。我想聪明的你应该会喜欢。
其实,如果我们将XxxTypeName常量设置为public的,再通过一个只读属性公开_allTypes集合,我想,我们又已经写好了一个类,而且完全可以投入使用了。
还不够,我还想要枚举
人的贪念是无限的。Anders Liu也是如此,Anders Liu吹毛求疵、追求那并不存在的完美。
为了使用起来更像枚举,我们再添加一系列静态属性:
/**//// <summary>
/// Word document.
/// </summary>
public static DocumentType Word
{
get { return _allTypes[WordTypeName]; }
}
/**//// <summary>
/// Html document.
/// </summary>
public static DocumentType Html
{
get { return _allTypes[HtmlTypeName]; }
}
/**//// <summary>
/// Text document.
/// </summary>
public static DocumentType Text
{
get { return _allTypes[TextTypeName]; }
}
#endregion
这样就明朗了吧?我想聪明的读者已经可以看出这个类型的使用场景了。
枚举还有一个特征,那就是可以与其底层的整数进行相互转换,还可以通过枚举成员的名字来得到枚举对象——这一切都不是非常负责。那么我们这个“仿枚举”,也应该如此。
我变,变字符串,变回来
这个简单,用自定义类型转换运算符就可以完成:
/**//// <summary>
/// Implicit convert <see cref="DocumentType"/> object to <see cref="string"/>.
/// </summary>
/// <param name="type">A given document type.</param>
/// <returns>The type name.</returns>
public static implicit operator string(DocumentType type)
{
return type.Name;
}
/**//// <summary>
/// Explicit convert <see cref="string"/> object to <see cref="DocumentType"/>.
/// </summary>
/// <param name="typeName">Given document type name.</param>
/// <returns>The corresponsive document type.</returns>
public static explicit operator DocumentType(string typeName)
{
if(_allTypes.ContainsKey(typeName))
return _allTypes[typeName];
else
throw new InvalidOperationException(string.Format("'{0}' is not a valid document type.", typeName));
}
#endregion
这些就不用多解释了,如果你不会编写自定义类型转换运算符,那可该补补C#了。
——看啊,抛异常了,显式转换的时候抛异常了!
——这有什么大惊小怪?很多时候做类型转换都会抛异常的。
——可是,抛异常影响效率……
——这……你这个人怎么比Anders Liu还追求“完美”啊?!
再完美一点
加一个判断,用来检测一个字符串是不是有效的文档类型名字,省得转换时抛异常:
/**//// <summary>
/// Indicates a given type name is valid or not.
/// </summary>
/// <param name="typeName">Given type name.</param>
/// <returns>Whether the <see cref="typeName"/> is vliad.</returns>