MyFramework:safe() 扩展函数的空集合设计

MyFramework 里有一个很小的扩展函数:

bash 复制代码
safe()

它经常出现在这种代码里:

bash 复制代码
foreach (var item in list.safe())
{
	// ...
}

这个函数本身很简单,但它解决的是框架代码里非常高频的问题:

集合可能为空,但遍历逻辑不应该到处写 null 判断。


一、代码

List<T> 的实现:

bash 复制代码
public class EmptyList<T>
{
	public static List<T> mList;
	public static List<T> getEmptyList()
	{
		mList ??= new();
		return mList;
	}
}

public static class ListExtension
{
	public static List<T> safe<T>(this List<T> original)
	{
		return original ?? EmptyList<T>.getEmptyList();
	}
}

数组也有对应实现:

bash 复制代码
public class EmptyArray<T>
{
	public static T[] mList;
	public static T[] getEmptyList()
	{
		mList ??= new T[0];
		return mList;
	}
}

public static class ArrayExtension
{
	public static T[] safe<T>(this T[] original)
	{
		return original ?? EmptyArray<T>.getEmptyList();
	}
}

字典和 HashSet 也一样:

bash 复制代码
public static Dictionary<TKey, TValue> safe<TKey, TValue>(this Dictionary<TKey, TValue> dic)
{
	return dic ?? EmptyDictionary<TKey, TValue>.getEmptyList();
}

public static HashSet<T> safe<T>(this HashSet<T> original)
{
	return original ?? EmptyHashSet<T>.getEmptyList();
}

二、用法

普通写法:

bash 复制代码
if (callbackList != null)
{
	foreach (var callback in callbackList)
	{
		callback();
	}
}

使用 safe() 后:

bash 复制代码
foreach (var callback in callbackList.safe())
{
	callback();
}

链式调用时更明显:

bash 复制代码
foreach (Type item in (mStateManager.getGroup(groupType)?.mStateList).safe())
{
	// ...
}

如果 getGroup(groupType) 返回 null,后面的 mStateList 也是 null。

safe() 会把 null 集合转换成空集合。

遍历逻辑不需要关心中间对象是否存在。


三、核心作用

safe() 的作用不是让集合"安全可写"。

它的核心作用是:

bash 复制代码
把 null 集合转换成空集合

这样遍历时可以统一写成:

bash 复制代码
foreach (var item in list.safe())
{
}

而不是每次都写:

bash 复制代码
if (list != null)
{
	foreach (var item in list)
	{
	}
}

它减少的是框架代码里的重复分支。


四、为什么不用 new 空集合

可以这样写:

bash 复制代码
return original ?? new List<T>();

但这样每次遇到 null 都会创建一个新对象。

safe() 使用的是泛型静态空集合:

bash 复制代码
EmptyList<T>.getEmptyList()

每种 T 只创建一个空列表。

比如:

bash 复制代码
EmptyList<int>
EmptyList<string>
EmptyList<GameObject>

它们各自有自己的静态空集合。

这样做的结果是:

bash 复制代码
不会反复分配空 List
不会增加无意义 GC
空集合可以被重复用于遍历

数组的 EmptyArray<T> 也是一样。


五、适合场景

safe() 最适合只读遍历。

比如:

bash 复制代码
foreach (Action item in mPositionModifyCallback.safe())
{
	item?.Invoke();
}

或者:

bash 复制代码
foreach (WindowObjectBase item in mChildList.safe())
{
	item.setVisible(false);
}

或者:

bash 复制代码
foreach (string pattern in excludePatterns.safe())
{
	// ...
}

这些场景里,null 和空集合的语义基本一致:

bash 复制代码
没有集合
等价于
没有元素需要处理

所以 safe() 很合适。


六、不是写入入口

safe() 返回的空集合是共享对象。

所以这类写法不应该出现:

bash 复制代码
list.safe().Add(item);

如果 list 是 null,这里添加的是 EmptyList<T> 里的共享空列表。

这会污染后续所有同类型的空集合遍历。

所以 safe() 的使用边界很明确:

bash 复制代码
用于遍历
用于读取
不用于写入

如果要写入,就应该先保证原集合存在:

bash 复制代码
list ??= new();
list.Add(item);

这点是使用 safe() 时最重要的约束。


七、和 isEmpty 的区别

MyFramework 里也有:

bash 复制代码
public static bool isEmpty<T>(this List<T> list)
{
	return list == null || list.Count == 0;
}

isEmpty() 适合判断:

bash 复制代码
if (list.isEmpty())
{
	return;
}

safe() 适合遍历:

bash 复制代码
foreach (var item in list.safe())
{
}

两者解决的问题不同。

isEmpty() 是分支判断。

safe() 是消除分支。


八、代码风格收益

在框架代码里,很多回调列表、组件列表、状态列表、子节点列表都可能为空。

如果每次遍历都写 null 判断,代码会很碎。

比如:

bash 复制代码
if (mChildList != null)
{
	foreach (var child in mChildList)
	{
		child.destroy();
	}
}

这种代码出现几百次以后,会明显增加阅读噪音。

使用 safe() 后:

bash 复制代码
foreach (var child in mChildList.safe())
{
	child.destroy();
}

重点直接落在遍历逻辑上。

这也是它的价值。

它不是复杂设计,但很适合框架代码。


九、空对象思想

safe() 本质上是一种很小的空对象设计。

null 集合不是特殊分支,而是被转换成一个空集合对象。

bash 复制代码
null List<T>
    ↓
EmptyList<T>

null T[]
    ↓
EmptyArray<T>

null Dictionary<TKey, TValue>
    ↓
EmptyDictionary<TKey, TValue>

null HashSet<T>
    ↓
EmptyHashSet<T>

这样调用者不需要关心 null。

只需要按正常集合遍历即可。


十、设计取舍

safe() 的优点很明显:

bash 复制代码
减少 null 判断
减少重复代码
避免空集合反复分配
让遍历代码更统一
链式调用时更简洁

限制也很明确:

bash 复制代码
只能用于读取和遍历
不能对返回的空集合写入
共享空集合不适合多线程写操作
不适合需要区分 null 和空集合语义的场景

所以它不是万能安全函数。

它是一个专门服务遍历场景的小工具。


总结

safe() 的实现很简单:

bash 复制代码
return original ?? EmptyList<T>.getEmptyList();

但它在框架代码里很实用。

它把大量这种代码:

bash 复制代码
if (list != null)
{
	foreach (var item in list)
	{
	}
}

压缩成:

bash 复制代码
foreach (var item in list.safe())
{
}

它的精巧点不在复杂,而在使用频率高、边界清楚、收益稳定。

在 MyFramework 里,safe() 适合用在大量只读遍历场景中。

它让 null 集合按空集合处理,减少分支,也避免重复创建临时空集合。

这就是 safe() 扩展函数的主要设计价值。

相关推荐
SmalBox6 小时前
【节点】[RoundedPolygon节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox1 天前
【节点】[Rectangle节点]原理解析与实际应用
unity3d·游戏开发·图形学
SmalBox2 天前
【节点】[Polygon节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_2 天前
MyFramework:整体代码结构与热更新分层解析
unity3d·游戏开发
甲维斯3 天前
Fable+Codex 《坦克大战3D》双端发布了!
人工智能·ai编程·游戏开发
SmalBox3 天前
【节点】[Houndstooth节点]原理解析与实际应用
unity3d·游戏开发·图形学
龙智DevSecOps解决方案5 天前
3A 游戏优化技术栈:如何打通引擎级分析工具与 DevOps 持续集成管线?
unity·性能优化·游戏开发·技术美术·perforce·unrealengine
SmalBox5 天前
【节点】[Herringbone节点]原理解析与实际应用
unity3d·游戏开发·图形学
_zhourui_h_5 天前
MyFramework:ClassPool 对象池与 resetProperty 的实现解析
unity3d