边看书边做边发挥-图书软件-8

时间:2021-12-17 17:46:43

第7篇的内容的丢失,确实出现了一个小断层,许多谈及的内容都没有了,现在再回忆起来却非常艰难,真可谓是说过话,马上就忘了

由于有了新的 BookBase 基类,FrameworkDesignGuidelinesBook 类很好的使用了,
但 WpfProgrammingValuables2012Book 类却没有很好的使用,这是因为 Catalogue 属性类型不匹配,上一篇做较大的篇幅说明这个原因,现在回忆一下,主要原因是,使用了两种模式来编写书的目录,
FrameworkDesignGuidelinesBook 类使用自定类 Catalogue 类 ,
而 WpfProgrammingValuables2012Book 类使用 LinkedList< Section>类来编写目录,
然而,WPF 桌面程序的树控件,不支持使用这个数据结构,他所使用的是一般的集合类,要分层这种数据结构,就要编写分层数据模板标签代码,上一篇编写的一个迭代器方法,为了迭代分层数据,自动添加前导空格而编写的一个 GetEnumeratorTreeView() 方法,却花费我了许多时间也没有成功,我设想能自动判断不管有多少层,自动添加前导空格,并使用一个递归方法和迭代器方法来实现,结果失败而告终

现在多么想恢复上一篇的数据,却也没有任何办法了,过去的记忆难于完美的恢复原状,许多细节的内容也无法想起来了

有一个地方可以找到实现这种方法的源代码,在 .NET 库里有 Xml 和 Xaml 的实现,他能很好的生成格式良好的 xml 文件或者 xaml 文件,包含前导空格也是可以设置的,于是,查看他的实现源代码,是个不错的模仿,现在找到这个实现细节,查看源代码的秘密武器是 ILSpy 软件,他使用反射技术来反射所有元数据,而且还是开源的,他能查看软件本身的源代码

边看书边做边发挥-图书软件-8

然而,查看实现源代码,可是个不简单的事情,找到 Xml 空间,找到 XmlNode 类,再找到 GeEnumerator() 方法,却发现使用了一个内部类

public IEnumerator GetEnumerator()
{
return new XmlChildEnumerator(this);
}

这时仍然没有找到,这时候找 XmlDocument 类,找 Save() 方法,几个重载版本都需要参数,查看基本中一个XmlWriter 类型参数的版本,而 XmlWriter 类是抽象类,要用静态方法 Create() 方法创建,Create() 重载版本基中一个需要 XmlWriterSettings 类型的参数,他可以设置缩进,似乎要找到,在内部他又调用 XmlWriterSettings 类的 CreateWriter() 方法,生成一个 FileStream 类的流和生成一个派生于 XmlWriter 类的 XmlUtf8RawTextWriterIndent 内部类,XmlUtf8RawTextWriterIndent 类 WriteStartElement() 方法内部每调用一次就会 indentLevel++,这是缩进,所以正常工作的是他的内部类,但我们不能使用这个类,只能通过多态的方式使用 XmlWriter 类变量,他在内部用他的派生类来来创建,我们调用 WriteStartElement() 方法时,他就调用内部的XmlUtf8RawTextWriterIndent.WriteStartElement() 方法

由引可见,这个空间的类是操作 xml 细节的工作,控制着 xml 的每一个元素,每一个特性,缩进,编码等等
进行更细的操作,也可以更暴力的操作,XmlDocument.Save() 方法完成所有工作,但是我的程序,只需要缩进,别一概不需要,我应该关心 XmlChildEnumerator 类的实现

原来,我使用 yield return 语句已经不能完成我的实现了,我需要编写自己的迭代器,进行更多判断,是否是第二层,如果是就添加前导空格,就是缩进,然后是否又是第三层,再叠加这个前导空格,以此类推,现在我需要完成自己编写一个迭代器,不管他有多少层,总是会控制添加缩进

在编写之前,应该回顾一下迭代器的原理,迭代器本质上是无限循环代码段,由 foreach 关键字使用,这里有两个重要接口,IEnumerator< out T> 接口用来返回集合中的当前项的 Current 属性,由于项的类型由客户代码定义,并且是可往上多态,又是泛型参数,要用 out 关键字进行协变,只能用来做返回值,还要判断如何移到下一项的MoveNext() 方法

IEnumeraable< out T>接口表示这个类或者结构,可以让foreach 关键字使用,事实上,不实现这个接口 foreach 关键字也能使用,只要有一个GetEnumerator() 方法,返回 IEnumerator 接口就能用了

在 Catalogue 类的自定义迭代 GetEnumeratorTreeView() 方法中,不是默认那个,默认那个调用GetEnumerator()方法,并且可以写语法糖,省略 GetEnumerator()方法调用, foreach 关键字等效如下代码段

            IEnumerable<string> cataloge = frameDesGuidlinesBook.Catalogue.GetEnumeratorTreeView();
IEnumerator<string> enumerator = cataloge.GetEnumerator();
while (enumerator.MoveNext())
{
string current = enumerator.Current;
Console.WriteLine(current);
}

首先会获得这个迭代器对象,然后使用 while 语句判断并移动下一项是否为真,条件为真时调用这个对象的 Current 属性,再调用客户代码要调用的代码,再重复,直到条件为假,退出迭代

在这里要判断 Section 类自身对象 Sections 属性集合是否有数据,如果有就添加缩进并返回他的名称, Sections 属性的每一项又有自己的 Sections 属性集合,又添加更多的缩进并返回他的名称,以此类推,这样的一种思想,就是递归,递归就是当需要不断的自我调用时用到,由于编码水平有限,并没有学习过任何数据结构,一开始使用起来有些困难,就是一些条件的编写起来怎么也不达不到要求,求助一些 C# 群的网友后,他们提供了 Linq 的 SelectMany 扩展方法 和 Aggregate 扩展方法才得以解决,感谢网络交流的强大,但仍然没有明白最基本的编码,这些扩展方法还是进一步封装了,我应该更深入了解,如何编写最基础的实现,仔细分析扩展方法的源代码,才发现递归的使用,光知道自我调用还是不够的,还需要如何传入参数,这才是比较关键的,参数的传入很重要,如果要不要判断自身,就应该传入一个自身类型参数,如果还需要其他可变的参数,比如缩进,就传入字符串,这样就当每次自我调用时,就从这参数获得信息,并判断一些条件和一些缩进叠加

修改 Section 类自定义迭代器,他是内部的,是为了 Chapter 类使用,并添加一个递归方法

        internal IEnumerable<string> GetEnumeratorTreeView(string padLeft, Section section)
{
return Aggregate(padLeft, section);
}

//把自身Sections 属性集合叠加起来返回一个集合,递归所有项,最终返回一个有层次的集合
private List<string> Aggregate(string padLeft, Section section)
{
var list = new List<string>();
list.Add(padLeft + section.name);
foreach (var s in section.sections)
{
var newPadLeft = padLeft + " ";
var nestList = Aggregate(newPadLeft, s);
list.AddRange(nestList);
}
return list;
}

也可以使用 Linq 扩展方法,就不需要额外的递归方法,而把 GetEnumeratorTreeView() 做为递归方法

        internal IEnumerable<string> GetEnumeratorTreeView(string padLeft, Section section)
{
yield return padLeft + name;
var newPadLeft = padLeft + " ";
foreach (var item in sections.SelectMany(s => s.GetEnumeratorTreeView(newPadLeft, section)))
{
yield return item;
}
}

修正 Chapter 类 GetEnumeratorTreeView() 方法

        public IEnumerable<string> GetEnumeratorTreeView()
{
var padLeft = " ";//左填充
foreach (var chapter in Items)
{
yield return chapter.Name;//首层,章节
foreach (var section in chapter.Sections)
{
//内层
foreach (var item in section.GetEnumeratorTreeView(padLeft, section))
{
yield return item;
}
}
}
}

现在可以不担心有多少层次结构了

边看书边做边发挥-图书软件-8