可复用的泛型工具
构建小型、可复用的辅助工具:保护性检查、基于键的相等性比较器、纯函数的记忆化,以及简单的 Result 。
可复用的泛型工具 是 CoddyKit 上的免费 C# Academy 课时。 这是第 3 节课,共 3 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 3 节课。
工具包计划
目标:收集可在各个项目中复用的小型辅助工具。
- 用于检查参数的Guard
- 用于相等性判断的基于键的比较器
- 用于记忆化纯函数的记忆化工具
- 用于成功/错误流程的Result<T>
保护性检查
将参数验证集中到一个小型的Guard类中,并在方法开始时调用它。
using System;
public static class Guard
{
public static T NotNull<T>(T value, string name) where T : class
{
if (value == null) throw new ArgumentNullException(name);
return value;
}
public static string NotNullOrEmpty(string value, string name)
{
if (string.IsNullOrEmpty(value)) throw new ArgumentException("must not be null or empty", name);
return value;
}
}
public class Program
{
public static void Main(string[] args)
{
// OK
string name = Guard.NotNullOrEmpty("Ada", "name");
Console.WriteLine("Hello, " + name);
// Uncomment to see guard throw:
// Guard.NotNull<string>(null, "input");
}
}
基于键的比较器
根据键比较复杂对象(例如标识),而不修改模型的 Equals/GetHashCode。
using System;
using System.Collections.Generic;
public sealed class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
private readonly Func<T, TKey> _keySelector;
private readonly IEqualityComparer<TKey> _keyComparer;
public KeyEqualityComparer(Func<T, TKey> keySelector)
: this(keySelector, EqualityComparer<TKey>.Default) {}
public KeyEqualityComparer(Func<T, TKey> keySelector, IEqualityComparer<TKey> keyComparer)
{
if (keySelector == null) throw new ArgumentNullException("keySelector");
if (keyComparer == null) throw new ArgumentNullException("keyComparer");
_keySelector = keySelector;
_keyComparer = keyComparer;
}
public bool Equals(T x, T y)
{
if (object.ReferenceEquals(x, y)) return true;
if ((object)x == null || (object)y == null) return false;
return _keyComparer.Equals(_keySelector(x), _keySelector(y));
}
public int GetHashCode(T obj)
{
if ((object)obj == null) return 0;
TKey key = _keySelector(obj);
return key == null ? 0 : _keyComparer.GetHashCode(key);
}
}
public sealed class User
{
public int Id;
public string Name;
}
public class Program
{
public static void Main(string[] args)
{
List<User> list = new List<User>();
list.Add(new User { Id = 1, Name = "Ada" });
list.Add(new User { Id = 1, Name = "Ada Lovelace" }); // same Id -> considered equal
list.Add(new User { Id = 2, Name = "Alan" });
// Deduplicate by Id using a HashSet with our comparer
HashSet<User> set = new HashSet<User>(new KeyEqualityComparer<User,int>(delegate(User u) { return u.Id; }));
foreach (User u in list) set.Add(u);
Console.WriteLine("Unique by Id = " + set.Count); // 2
}
}
记忆化辅助方法
记忆化会缓存纯函数的结果。对于使用相同输入的重复调用,可以使用它。
using System;
using System.Collections.Generic;
public static class Memo
{
// Wrap a pure function f: K -> V with a simple cache
public static Func<TKey, TValue> Memoize<TKey, TValue>(Func<TKey, TValue> f)
{
if (f == null) throw new ArgumentNullException("f");
Dictionary<TKey, TValue> cache = new Dictionary<TKey, TValue>();
return delegate(TKey key)
{
TValue v;
if (cache.TryGetValue(key, out v)) return v;
v = f(key);
cache[key] = v;
return v;
};
}
}
public class Program
{
static int SlowSquare(int x)
{
// Simulate work
System.Threading.Thread.Sleep(100);
return x * x;
}
public static void Main(string[] args)
{
Func<int,int> fast = Memo.Memoize<int,int>(SlowSquare);
var t1 = DateTime.UtcNow; Console.WriteLine(fast(12));
var t2 = DateTime.UtcNow; Console.WriteLine(fast(12)); // from cache
var dt1 = (t2 - t1).TotalMilliseconds;
var t3 = DateTime.UtcNow; Console.WriteLine(fast(12)); // from cache again
var t4 = DateTime.UtcNow; var dt2 = (t4 - t3).TotalMilliseconds;
Console.WriteLine("First vs cached ms: " + dt1 + " / " + dt2);
}
}
Result<T> 包装器
最小化的Result<T>类型可以明确表示成功或错误,并且易于打印或测试。
using System;
public enum ResultKind { Ok, Error }
public sealed class Result<T>
{
public ResultKind Kind;
public T Value; // valid when Ok
public string Error; // valid when Error
private Result() {}
public static Result<T> Ok(T value)
{
Result<T> r = new Result<T>();
r.Kind = ResultKind.Ok;
r.Value = value;
r.Error = null;
return r;
}
public static Result<T> Fail(string error)
{
if (string.IsNullOrEmpty(error)) throw new ArgumentException("error");
Result<T> r = new Result<T>();
r.Kind = ResultKind.Error;
r.Error = error;
r.Value = default(T);
return r;
}
public override string ToString()
{
return Kind == ResultKind.Ok ? "OK: " + (Value == null ? "null" : Value.ToString())
: "ERROR: " + Error;
}
}
public class Program
{
static Result<int> ParsePositiveInt(string s)
{
int n;
if (!Int32.TryParse(s, out n)) return Result<int>.Fail("not a number");
if (n <= 0) return Result<int>.Fail("must be > 0");
return Result<int>.Ok(n);
}
public static void Main(string[] args)
{
Console.WriteLine(ParsePositiveInt("12"));
Console.WriteLine(ParsePositiveInt("-1"));
Console.WriteLine(ParsePositiveInt("x"));
}
}
实用提示
提示:
- 保持辅助工具小巧且职责明确。
- 使用清晰的名称:Guard、KeyEqualityComparer、Memo。
- 说明约束以及使用时机(Memo 适用于纯函数)。
- 为每个辅助工具编写一个快速单元测试。
键比较器的作用
回顾
回顾:复用小型工具——使用Guard处理输入,使用KeyEqualityComparer处理相等性,使用Memo处理纯函数,使用Result<T>表示明确的结果。
常见问题解答
「可复用的泛型工具」课时是免费的吗?
是的 — 「可复用的泛型工具」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 3 节课。
「可复用的泛型工具」这节课中我会学到什么?
构建小型、可复用的辅助工具:保护性检查、基于键的相等性比较器、纯函数的记忆化,以及简单的 Result 。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 3 节。
「可复用的泛型工具」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。