好的,现在我有一些时间来实际研究 Josh 建议的反射方法的实现。基本代码(在我的ConsoleIntercepter
类中,继承自TextWriter
)是:
private static object GetConsoleLockObject()
{
object lockObject;
try
{
const BindingFlags bindingFlags = BindingFlags.GetProperty |
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public;
// It's currently private, but we'd be happy if it were public, too.
Type consoleType = typeof(Console);
lockObject = consoleType.InvokeMember("InternalSyncObject", bindingFlags,
null, null, null);
}
catch
{
lockObject = null; // Return null on any failure.
}
return lockObject;
}
public static void RegisterConsoleIntercepter()
{
object lockObject = GetConsoleLockObject();
if (lockObject != null)
{
// Great! We can make sure any other changes happen before we read
// or after we've written, making this an atomic replacement operation.
lock (lockObject)
{
DoIntercepterRegistration();
}
}
else
{
// Couldn't get the lock object, but we still need to work, so
// just do it without an outer lock, and keep your fingers crossed.
DoIntercepterRegistration();
}
}
然后DoIntercepterRegistration()
会是这样的:
private static void DoIntercepterRegistration()
{
Console.SetOut(new ConsoleIntercepter(Console.Out));
Console.SetError(new ConsoleIntercepter(Console.Error));
}
这只是为了对 Out 和 Error 进行锁定保护的原子替换;显然,实际的拦截、处理和传递给前一个TextWriter
s 需要额外的代码,但这不是这个问题的一部分。
Note that (in the source code provided for .NET2.0) the Console class uses InternalSyncObject to protect initialization of Out and Error and to protect SetOut() and SetError(), but it does not use the lock around reads of Out and Error once they have been previously initialized. This is sufficient for my particular case, but could have atomicitiy violations if you did something more complex (and crazy); I can't think of any useful scenarios with that problem, but deliberately pathological ones can be imagined.