前两篇写了 MyFramework 里一些常用的代码简化技巧。
这一篇继续写第三组。
不过这次只写真正能让调用侧代码变短的东西。
也就是一眼能看到:
原本要写几行,现在一行就能表达清楚。
这类工具不是复杂系统。
它们更多是项目长期开发中沉淀出来的小函数、小扩展、小短写。
单独看都不大,但使用频率很高。
项目地址:
https://github.com/ZHOURUIH/MyFramework
一、字符串和数字转换短写
项目里数字和字符串互转非常常见。
比如:
- 配置表解析
- UI 文本显示
- 日志输出
- 协议调试
- 坐标输出
- 百分比显示
- bool 状态输出
原始写法通常是这样:
int id = int.Parse(idString); float speed = float.Parse(speedString); string valueText = value.ToString("F2"); string flag = enable ? "true" : "false";这些代码本身不难。
但问题是项目里到处都要写。
MyFramework 最新版本里,这类转换已经大量改成扩展方法。
比如字符串转整数:
int id = idString.SToI();字符串转浮点:
float speed = speedString.SToF();整数转字符串:
string idText = id.IToS();long 转字符串:
string guidText = guid.LToS();浮点转字符串,并指定精度:
string valueText = value.FToS(2);bool 转字符串:
string flag = enable.boolToString();这类写法的价值不是 C# 原生做不到,而是项目里需要统一。
比如 bool 转字符串,如果到处写:
string flag = enable ? "true" : "false";后面又有人写:
string flag = enable ? "True" : "False";再有人写:
string flag = enable.ToString();项目里的输出格式就不统一。
统一成:
string flag = enable.boolToString();以后风格就固定了。
真实代码里,bool 转字符串是这样实现的:
public static string boolToString(this bool value, bool firstUpper = false, bool fullUpper = false) { if (fullUpper) { return value ? "TRUE" : "FALSE"; } if (firstUpper) { return value ? "True" : "False"; } return value ? "true" : "false"; }百分比显示也是一样。
原本写法:
string percent = (value * 100.0f).ToString("F1") + "%";现在写法:
string percent = value.toPercent();或者指定精度:
string percent = value.toPercent(1);真实代码:
public static string toPercent(this string value, int precision = 1) { return (value.SToF() * 100).FToS(precision) + "%"; } public static string toPercent(this float value, int precision = 1) { return (value * 100).FToS(precision) + "%"; }向量转字符串也统一成扩展方法。
原本写法:
string posText = pos.x.ToString("F2") + "," + pos.y.ToString("F2") + "," + pos.z.ToString("F2");现在写法:
string posText = pos.V3ToS(2);真实代码:
public static string V3ToS(this Vector3 value, int precision = 4) { return strcat(value.x.FToS(precision), ",", value.y.FToS(precision), ",", value.z.FToS(precision)); }反过来,字符串转向量也可以一行完成。
原本写法:
string[] splitList = str.Split(','); Vector3 pos = new(float.Parse(splitList[0]), float.Parse(splitList[1]), float.Parse(splitList[2]));现在写法:
Vector3 pos = str.SToV3();真实代码:
public static Vector3 SToV3(this string str, char separate = ',') { if (str.isEmpty() || str == "0,0,0") { return Vector3.zero; } string[] splitList = str.split(separate); if (splitList.count() < 3) { return Vector3.zero; } return new(splitList[0].SToF(), splitList[1].SToF(), splitList[2].SToF()); }这类短函数的意义很明确:
把常见转换写成统一入口。
它不会让逻辑变复杂。
只是让代码更短、更统一。
二、List 操作短写
List<T>是项目里最常用的容器之一。
很多 List 操作原本都要写好几行。
比如条件添加。
原本写法:
if (condition) { list.Add(value); }现在写法:
list.addIf(value, condition);真实代码:
public static bool addIf<T>(this List<T> list, T value, bool condition) { if (condition) { list.Add(value); } return condition; }非空才添加。
原本写法:
if (value != null) { list.Add(value); }现在写法:
list.addNotNull(value);真实代码:
public static bool addNotNull<T>(this List<T> list, T value) where T : class { if (value != null) { list.Add(value); return true; } return false; }字符串非空才添加。
原本写法:
if (!text.isEmpty()) { list.Add(text); }现在写法:
list.addNotEmpty(text);真实代码:
public static bool addNotEmpty(this List<string> list, string value) { if (!value.isEmpty()) { list.Add(value); return true; } return false; }不重复添加。
原本写法:
if (!list.Contains(value)) { list.Add(value); }现在写法:
list.addUnique(value);真实代码:
public static bool addUnique<T>(this List<T> list, T value) { if (!list.Contains(value)) { list.Add(value); return true; } return false; }有条件地不重复添加。
原本写法:
if (condition && !list.Contains(value)) { list.Add(value); }现在写法:
list.addUniqueIf(value, condition);真实代码:
public static bool addUniqueIf<T>(this List<T> list, T value, bool condition) { if (!condition) { return false; } if (!list.Contains(value)) { list.Add(value); return true; } return false; }根据状态添加或移除。
原本写法:
if (enable) { if (!list.Contains(value)) { list.Add(value); } } else { list.Remove(value); }现在写法:
list.addUniqueOrRemove(value, enable);真实代码:
public static void addUniqueOrRemove<T>(this List<T> list, T value, bool addOrRemove) { if (addOrRemove) { list.addUnique(value); } else { list.Remove(value); } }移除并返回被移除的元素。
原本写法:
T value = list[index]; list.RemoveAt(index); return value;现在写法:
return list.removeAt(index);真实代码:
public static T removeAt<T>(this List<T> list, int index) { T value = list[index]; list.RemoveAt(index); return value; }不关心顺序时,可以交换到末尾再删除。
现在写法:
return list.swapToEndAndRemove(index);真实代码:
public static T swapToEndAndRemove<T>(this List<T> list, int index) { list.swap(index, list.Count - 1); return list.removeAt(list.Count - 1); }创建新对象并添加到列表。
原本写法:
Item item = new(); list.Add(item); item.init();现在写法:
list.addNew<Item>().init();真实代码:
public static T addNew<T>(this List<T> list) where T : new() { return list.add(new()); }如果添加的是对象池对象,也可以写:
list.addClass<Item>().init();真实代码:
public static T addClass<T>(this List<T> list) where T : ClassObject, new() { return list.add(CLASS<T>()); }取最后一个元素。
原本写法:
T value = list == null || list.Count == 0 ? default : list[list.Count - 1];现在写法:
T value = list.getLast();这类扩展函数都不复杂。
但它们解决的是同一种问题:
不要让业务代码里到处充满重复的 if、Contains、Add、RemoveAt。
比如:
list.addUnique(value);这一行表达得很清楚:
把 value 添加进列表,但不要重复。
这比每个地方都手写Contains + Add更稳定,也更统一。
三、Dictionary 操作短写
Dictionary的重复代码也很多。
最常见的是安全取值。
原本写法:
if (!dic.TryGetValue(key, out Value value)) { value = defaultValue; }现在写法:
Value value = dic.get(key, defaultValue);真实代码:
public static Value get<Key, Value>(this Dictionary<Key, Value> map, Key key, Value defaultValue) { return map != null && map.TryGetValue(key, out Value value) ? value : defaultValue; }不需要默认值时:
Value value = dic.get(key);真实代码:
public static Value get<Key, Value>(this Dictionary<Key, Value> map, Key key) { if (map == null) { return default; } map.TryGetValue(key, out Value value); return value; }添加或覆盖。
原本写法:
dic[key] = value;现在写法:
dic.addOrSet(key, value);真实代码:
public static void addOrSet<TKey, TValue>(this Dictionary<TKey, TValue> dic, TKey key, TValue value) { dic[key] = value; }这个看起来只是换了名字,但它表达更明确:
这个操作允许新增,也允许覆盖。
数值累加是更明显的简化。
原本写法:
if (dic.TryGetValue(key, out int curValue)) { dic[key] = curValue + increase; } else { dic.Add(key, increase); }现在写法:
dic.addOrIncreaseValue(key, increase);真实代码:
public static void addOrIncreaseValue<TKey>(this Dictionary<TKey, int> dic, TKey key, int increase) { if (dic.TryGetValue(key, out int curValue)) { dic[key] = curValue + increase; } else { dic.Add(key, increase); } }根据状态添加或移除。
原本写法:
if (isAdd) { dic.Add(key, value); } else { dic.Remove(key); }现在写法:
dic.addOrRemove(key, value, isAdd);真实代码:
public static void addOrRemove<Key, Value>(this Dictionary<Key, Value> map, Key key, Value value, bool isAdd) { if (isAdd) { map.Add(key, value); } else { map.Remove(key); } }获取或创建普通对象。
原本写法:
if (!map.TryGetValue(key, out Value value)) { value = new(); map.Add(key, value); }现在写法:
map.getOrAddNew(key, out Value value);或者:
Value value = map.getOrAddNew(key);真实代码:
public static bool getOrAddNew<Key, Value>(this Dictionary<Key, Value> map, Key key, out Value value) where Value : new() { if (!map.TryGetValue(key, out value)) { value = new(); map.Add(key, value); return false; } return true; } public static Value getOrAddNew<Key, Value>(this Dictionary<Key, Value> map, Key key) where Value : new() { if (!map.TryGetValue(key, out Value value)) { value = new(); map.Add(key, value); } return value; }获取或创建对象池对象。
原本写法:
if (!map.TryGetValue(key, out T value)) { CLASS(out value); map.Add(key, value); }现在写法:
T value = map.getOrAddClass(key);真实代码:
public static T getOrAddClass<Key, T>(this Dictionary<Key, T> map, Key key) where T : ClassObject, new() { if (!map.TryGetValue(key, out T value)) { map.Add(key, CLASS(out value)); } return value; }这类写法在事件系统、点击系统、资源系统里都很常见。
比如事件监听列表:
mGlobalListenerEventList.getOrAddClass(info.mEventTypeID).add(info);这行代码背后做了三件事:
- 根据事件类型查找监听列表
- 如果没有,就从对象池创建一个
- 然后把监听信息加进去
如果不封装,代码会变成一大段TryGetValue + CLASS + Add。
现在一行就能表达清楚。
Dictionary 这类短写非常适合框架代码。
因为框架里经常会按 ID、类型、路径、状态分组。
这些分组逻辑如果每次都手写字典创建,代码会非常啰嗦。
四、字符串截取和处理短写
字符串截取也是项目里很常见的重复代码。
尤其是路径、文件名、配置字段、资源名、日志文本。
比如截取某个字符之前的内容。
原本写法:
int index = str.IndexOf('/'); if (index >= 0) { str = str[0..index]; }现在写法:
str = str.rangeToFirst('/');真实代码:
public static string rangeToFirst(this string str, char key) { if (str == null) { return str; } int endIndex = str.IndexOf(key); if (endIndex >= 0) { return str[0..endIndex]; } return str; }截取某个字符之后的内容。
现在写法:
str = str.rangeFromFirst('/');真实代码:
public static string rangeFromFirst(this string str, char key) { if (str == null) { return str; } int startIndex = str.IndexOf(key); if (startIndex < 0) { return str; } return str[(startIndex + 1)..]; }截取两个字符之间的内容。
原本写法:
int startIndex = str.IndexOf('('); int endIndex = str.IndexOf(')'); if (startIndex >= 0 && endIndex >= 0 && endIndex > startIndex) { str = str[(startIndex + 1)..endIndex]; }现在写法:
str = str.rangeBetweenKeyToKey('(', ')');真实代码:
public static string rangeBetweenKeyToKey(this string str, char key0, char key1) { if (str == null) { return str; } int startIndex = str.IndexOf(key0); int endIndex = str.IndexOf(key1); if (startIndex < 0 || endIndex < 0 || endIndex < startIndex) { return str; } return str[(startIndex + 1)..endIndex]; }移除前缀。
原本写法:
if (str != null && str.StartsWith(prefix)) { str = str[prefix.Length..]; }现在写法:
str = str.removeStartString(prefix);真实代码:
public static string removeStartString(this string str, string pattern, bool caseSensitive = true) { if (str == null || pattern == null || str.Length < pattern.Length) { return str; } bool needRemove; if (caseSensitive) { needRemove = str.StartsWith(pattern); } else { needRemove = str.ToLower().StartsWith(pattern.ToLower()); } if (needRemove) { return str[pattern.Length..]; } return str; }移除后缀。
原本写法:
if (str != null && str.EndsWith(suffix)) { str = str[0..(str.Length - suffix.Length)]; }现在写法:
str = str.removeEndString(suffix);真实代码:
public static string removeEndString(this string str, string pattern, bool caseSensitive = true) { if (str == null || pattern == null || str.Length < pattern.Length) { return str; } bool needRemove; if (caseSensitive) { needRemove = str.EndsWith(pattern); } else { needRemove = str.ToLower().EndsWith(pattern.ToLower()); } if (needRemove) { return str[0..(str.Length - pattern.Length)]; } return str; }确保前缀存在。
原本写法:
if (!path.StartsWith("Assets/")) { path = "Assets/" + path; }现在写法:
path = path.ensurePrefix("Assets/");真实代码:
public static string ensurePrefix(this string str, string prefix) { if (!str.StartsWith(prefix)) { return prefix + str; } return str; }确保后缀存在。
原本写法:
if (!path.EndsWith("/")) { path += "/"; }现在写法:
path = path.ensureSuffix("/");真实代码:
public static string ensureSuffix(this string str, string prefix) { if (!str.EndsWith(prefix)) { return str + prefix; } return str; }这类短写非常适合路径处理。
因为路径处理里经常要做这些事:
- 去掉前缀
- 去掉后缀
- 截取文件夹
- 截取文件名
- 保证路径以
/结尾 - 保证路径以
Assets/开头 - 截取某个标记之间的内容
如果每个地方都写IndexOf、Substring、StartsWith、EndsWith,代码会非常散。
封装成字符串扩展以后,调用侧就更像自然语言。
比如:
path = path.ensureSuffix("/");这行代码一眼就能看懂:
确保 path 有/后缀。
这就是代码简化的意义。
五、数组、Span、byte[] 操作短写
数组访问经常要做边界判断。
原本写法:
T value = default; if (array != null && index >= 0 && index < array.Length) { value = array[index]; }现在写法:
T value = array.get(index);真实代码:
public static T get<T>(this T[] list, int index) { if (list.isEmpty() || index < 0 || index >= list.Length) { return default; } return list[index]; }数组设置也是一样。
原本写法:
if (array != null && index >= 0 && index < array.Length) { array[index] = value; }现在写法:
array.set(index, value);真实代码:
public static void set<T>(this T[] list, int index, T value) { if (list.isEmpty() || index < 0 || index >= list.Length) { return; } list[index] = value; }取第一个元素。
原本写法:
T value = array == null || array.Length == 0 ? default : array[0];现在写法:
T value = array.first();真实代码:
public static T first<T>(this T[] list) { if (list.isEmpty()) { return default; } return list[0]; }数组安全遍历。
原本写法:
if (array != null) { foreach (var item in array) { ... } }现在写法:
foreach (var item in array.safe()) { ... }真实代码:
public static T[] safe<T>(this T[] original) { return original ?? EmptyArray<T>.getEmptyList(); }判断数组里是否包含某个值。
原本写法:
bool contains = false; if (array != null) { for (int i = 0; i < array.Length; ++i) { if (equal(array[i], value)) { contains = true; break; } } }现在写法:
bool contains = array.contains(value);真实代码:
public static bool contains<T>(this T[] list, T value) { if (list.isEmpty()) { return false; } int length = list.Length; for (int i = 0; i < length; ++i) { if (equal(list[i], value)) { return true; } } return false; }byte[] 转字符串也可以短写。
原本写法:
string text = Encoding.UTF8.GetString(bytes);现在写法:
string text = bytes.bytesToString();真实代码:
public static string bytesToString(this byte[] bytes, Encoding encoding = null) { if (bytes.isEmpty()) { return string.Empty; } // 默认为UTF8 return removeLastZero((encoding ?? Encoding.UTF8).GetString(bytes)); }Span<byte> 也可以保持同样写法:
string text = span.bytesToString();真实代码:
public static string bytesToString(this Span<byte> bytes, Encoding encoding = null) { if (bytes == null) { return null; } if (bytes.Length == 0) { return string.Empty; } // 默认为UTF8 return removeLastZero((encoding ?? Encoding.UTF8).GetString(bytes)); }这类短写解决的是两个问题。
第一,减少边界判断。
第二,统一常用转换。
数组访问如果每次都手写边界判断,代码会很长。
但如果不判断,又容易越界。
所以框架提供get()、set()、first()这种短写以后,调用侧更简单。
byte[] 转字符串也是同理。
项目里经常会有网络数据、文件数据、配置数据需要转字符串。
如果到处直接写Encoding.UTF8.GetString(bytes),还要考虑空数组、尾部 0、编码默认值等问题。
统一写成:
bytes.bytesToString();调用侧就更干净。
总结
这一篇基于 MyFramework 最新版本,重新调整了 5 类真正能让调用侧变短的技巧:
- 字符串和数字转换短写
- List 操作短写
- Dictionary 操作短写
- 字符串截取和处理短写
- 数组、Span、byte[] 操作短写
这一版里要特别注意,很多转换函数已经是扩展方法写法。
比如:
idString.SToI(); speedString.SToF(); id.IToS(); guid.LToS(); value.FToS(2); enable.boolToString(); pos.V3ToS(2); "0.5".toPercent(); 0.5f.toPercent();它们的共同点是:
原本要写几行的固定模式,现在可以压成一行。
比如:
if (!list.Contains(value)) { list.Add(value); }变成:
list.addUnique(value);比如:
if (dic.TryGetValue(key, out int curValue)) { dic[key] = curValue + increase; } else { dic.Add(key, increase); }变成:
dic.addOrIncreaseValue(key, increase);比如:
int index = str.IndexOf('/'); if (index >= 0) { str = str[0..index]; }变成:
str = str.rangeToFirst('/');比如:
if (array != null && index >= 0 && index < array.Length) { value = array[index]; }变成:
value = array.get(index);这些不是为了隐藏业务逻辑。
它们隐藏的是每天都会重复出现的样板代码。
一句话总结:
代码简化不是少写业务。
而是不要让业务代码被重复的固定写法淹没。