我最近在造一个比 Excel 差得多的表格控件,其中一个需求是属性的继承。大家都知道,表格里面有单元格,单元格里面允许放文本,文本可以放多段文本。本文的主角就是文本段落的样式属性,包括文本字体字号颜色等等属性。文本段落的属性,如果没有特别设置,将使用单元格里面的文本样式属性。而如果单元格里面,没有特别指定此单元格使用特殊的文本样式,将会继承使用当前所在的行的文本样式。如果当前行没有特殊指定文本样式属性,那么将会使用文档的默认样式。文档默认样式将会根据是否有特殊指定而采用主题样式 如此复杂的层层继承逻辑,如果每个属性都需要自己一层层去寻找,那代码量将会特别多。维护起来就想吃桌子
为了保住桌子,咱来写一个支持层层继承属性的对象。如在当前层找不到某个属性,将会往上一层自动去找,一层层找。如果都找不到,将返回默认值
以下是这个类的定义代码
public class FlattenObject
{
/// <summary>
/// 创建带继承的对象
/// </summary>
/// <param name="reserved"></param>
public FlattenObject(FlattenObject? reserved = null)
{
Reserved = reserved;
}
private FlattenObject? Reserved { get; }
private Dictionary<string, object> ValueDictionary { get; } = new Dictionary<string, object>();
/// <summary>
/// 设置属性值
/// </summary>
/// <param name="value"></param>
/// <param name="propertyName"></param>
protected void SetValue(object value, [CallerMemberName] string propertyName = null!)
{
ValueDictionary[propertyName] = value;
}
/// <summary>
/// 获取属性值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="propertyName"></param>
/// <returns></returns>
protected T? GetValue<T>([CallerMemberName] string propertyName = null!)
=> GetValue<T>(default!, propertyName);
/// <summary>
/// 获取属性值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="defaultValue"></param>
/// <param name="propertyName"></param>
/// <returns></returns>
protected T GetValue<T>(T defaultValue, [CallerMemberName] string propertyName = null!)
{
if (ValueDictionary.TryGetValue(propertyName, out var value))
{
return (T) value;
}
else
{
if (Reserved is not null)
{
return Reserved.GetValue<T>(defaultValue, propertyName);
}
else
{
return defaultValue;
}
}
}
}
通过 Reserved 属性表示的是当前层的上一层的对象,用于给当前层进行继承。因为每一层都包含了上一层的对象,因此从最下层就可以一层层自动找到属性的值
继承当前类型,即可写出下面代码
class FooFlattenObject : FlattenObject
{
public FooFlattenObject(FlattenObject reserved = null) : base(reserved)
{
}
public string FontName
{
set => SetValue(value);
get => GetValue<string>();
}
public int Count
{
set => SetValue(value);
get => GetValue<int>();
}
}
如上面代码,在各个属性的 set 和 get 都换成调用方法,而不需要定义字段
下面来尝试写单元测试
"给定可继承的对象,可以从继承的对象拿到属性值".Test(() =>
{
var reserved = new FooFlattenObject()
{
FontName = "微软雅黑"
};
var fakeFlattenObject = new FooFlattenObject(reserved);
Assert.AreEqual("微软雅黑", fakeFlattenObject.FontName);
fakeFlattenObject.Count = 2;
Assert.AreEqual(2, fakeFlattenObject.Count);
Assert.AreEqual(0, reserved.Count);
});
可以看到在 reserved 对象里面设置了 FontName 的值,可以被 fakeFlattenObject 继承拿到,同时自动读取的代码对于上层业务来说几乎没有
对 fakeFlattenObject 进行设置 Count 的值,不会影响到 reserved 对象
通过此方法可以让存在层层继承逻辑的代码不需要大量重复。除了在表格上使用,也可以用在如解析 PPT 的形状内文本,如 PPT 的图片裁剪等需要继承属性的逻辑上
上面的代码也存在不足,那就是对于结构体不友好,如 bool 或 int 等类型,都需要转换为 object 存放
本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-%E5%86%99%E4%B8%80%E4%B8%AA%E6%94%AF%E6%8C%81%E5%B1%82%E5%B1%82%E7%BB%A7%E6%89%BF%E5%B1%9E%E6%80%A7%E7%9A%84%E5%AF%B9%E8%B1%A1.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。
如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者收藏我的博客导航
本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 。
无盈利,不卖课,做纯粹的技术博客
以下是广告时间
推荐关注 Edi.Wang 的公众号
欢迎进入 Eleven 老师组建的 .NET 社区
以上广告全是友情推广,无盈利