每周源代码27 - 让类库更强大

我最近对运用插件和其他工具扩展应用程序越来越有兴趣了。秉持着我一贯的信念——阅读代码以开发更完美的程序—亲爱的读者们,这里我为你们奉上第26篇《每周源代码》,(半年了!)之后还会不断继续。

我致力于.NET Base Class Library(BCL)也已经有六年时间了,我对我喜欢的class还是很有感觉的,比如哪些很好用,哪些感觉不是很妙。我也在学习LINQ以及其他各种扩展的方法,它们会让代码读起来更舒服。我还有很多想法,怎样去修复/完善/改进framework,不过说真的,谁有这个时间呢?显然我要提到的这些人就有。所以这周的每周源代码就献给那些为了保证其它类库使用更顺畅而创建类库的人们。

Umbrella

https://www.nventive.net/上的同行们创建了一个叫做Umbrella的项目,它是针对.NET BCL而设的一系列辅助类,目的在于“减少阻碍,增强API的可预见性”。他们还计划推出适用于Unity,Entity Framework,WCF和企业库的加速器。

它涉及面广,包含了上百种新函数及辅助类。不过我觉得他们不指望你去“学习”Umbrella,而是希望你们能“跌”进去,当然是褒义的“跌”。这就好像是沿用了“成功的坑”这一概念。有什么比掉下去更简单的?它的想法是如果你设计的API准确无误,你的同行们就会轻易地掉进“成功”这个“陷阱”。

记得去关注下这个项目,读起来真的很有趣。这里提供一些很棒的实例:

以下是些ICollection的扩展。

    1: namespace nVentive.Umbrella.Extensions
    2: {
    3:     public static class CollectionExtensions
    4:     {
    5:         public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> items)
    6:         {
    7:             items.ForEach(collection.Add);
    8:         }
    9:         public static void Remove<T>(this ICollection<T> collection, Func<T, bool> predicate)
   10:         {
   11:             collection.Where(predicate).ToArray().ForEach(item => collection.Remove(item));
   12:         }
   13:         public static ICollection<U> Adapt<T, U>(this ICollection<T> collection)
   14:         {
   15:             return Adapt<T, U>(collection, Funcs<U, T>.Convert, Funcs<T, U>.Convert);
   16:         }
   17:         public static ICollection<U> Adapt<T, U>(this ICollection<T> collection, Func<U, T> from, Func<T, U> to)
   18:         {
   19:             return new CollectionAdapter<T, U>(collection, from, to);
   20:         }
   21:         public static void ReplaceWith<T>(this ICollection<T> collection, IEnumerable<T> items)
   22:         {
   23:             collection.Clear();
   24:             AddRange<T>(collection, items);
   25:         }
   26:         public static IDisposable Subscribe<T>(this ICollection<T> collection, T item)
   27:         {
   28:             collection.Add(item);
   29:             return Actions.Create(() => collection.Remove(item)).ToDisposable();
   30:         }
   31:         public static void AddDistinct<T>(this ICollection<T> collection, T item)
   32:         {
   33:             AddDistinct<T>(collection, item, Predicates<T>.Equal);
   34:         }
   35:         public static void AddDistinct<T>(this ICollection<T> collection, T item, IEqualityComparer<T> comparer)
   36:         {
   37:             AddDistinct<T>(collection, item, comparer.Equals);
   38:         }
   39:         public static void AddDistinct<T>(this ICollection<T> collection, T item, Func<T, T, bool> predicate)
   40:         {
   41:             if (collection.None(collectionItem => predicate(collectionItem, item)))
   42:             {
   43:                 collection.Add(item);
   44:             }
   45:         }
   46:     }
   47: }
   48:  

这里出现的像AddRange()之类的方法,会让你情不自禁地去想,它们之前怎么没有。而像Remove()方法的判定参数(predicate),指示哪些项目需要被移除,是非常智能且清晰的。在接下来的例子中,它指示移除能被2整除的数字。

    1: [Fact]
    2: public void Remove()
    3: {
    4:     collection.AddRange(new int[] { 1, 2, 3, 4 });
    5:     collection.Remove(item => item % 2 == 0);
    6:     Assert.Equal(2, collection.Count);
    7:     Assert.Equal(1, collection.ElementAt(0));
    8:     Assert.Equal(3, collection.ElementAt(1));
    9: }

还有一个很有趣的实例就是他们的ReflectionExtensions,让Reflection变得更清晰了。以下是其测试:

    1: [Fact]
    2: public void Instance()
    3: {
    4:     Foo foo = new Foo();
    5:     IReflectionExtensionPoint fooReflection = foo.Reflection();
    6:     Assert.Equal(foo.I, fooReflection.Get("i"));
    7:     fooReflection.Set("i", 2);
    8:     Assert.Equal(2, foo.I);
    9:     Assert.Equal(2, fooReflection.Get("I"));
   10:     fooReflection.Set("I", 3);
   11:     Assert.Equal(3, foo.I);
   12:     Assert.Equal(3, fooReflection.Get("GetI"));
   13:     fooReflection.Set("SetI", 4);
   14:     Assert.Equal(4, foo.I);
   15: }

当然,这些只是触及了Umbrella的皮毛而已,它其实深不可测。

Ukadc.Diagnostics

连整个开发团队都明白,就像他们说的,“这是个好东西,却有个烂名字”。托管在Codeplex上的Ukadc.Diagnostics,其实是System.Diagnostics的扩展库。为什么呢?Josh Twist是这样阐述的:

故事来自微软英国应用程序开发顾问团(UKADC)的同事。这个项目的产生源于我和Morgan Skinner的一段对话,当时,我提及想用log4netnlog或者企业库记录未被.NET 2.0及以上版本封装的一些东西。。

作为一个资深的log4net人员,我还是跟以往一样,去查看他们的相关实例或者完整资源。相关实例展示了smtpTraceListener和sqlTraceListener,其中涵盖了很好的例子告诉你怎样使用Trace.CorrelationManager,来实现整个完整的HTTP请求周期的上下文追踪。

这个类库给TraceListener和CustomTracelistener提供了更好的基类,你只要重写(override)两个函数就能轻松创建你自己的trace listener。我建议你去尝试下。

话说回来,我在读他们代码的时候,让我视为精华的其实并不是这个库的直截了当,而是它实现了Fast Property Getter,能快速获取属性(Property)。你在说什么?呵呵,想一下吧,有时你用Reflection来获取一个对象的属性,首先你要实现它,然后才能使用PropertyInfo对象,这个过程太慢。大多数同行们至少会缓存一下PropertyInfo,这会让你免于重复初始化,但当你用它获取对象的值(value)的时候,你还是会付出一点时间代价的。

DynamicPropertyReader类把CLR type和property name都作为一个字符串看待。它会选择这个FastPropertyGetter,以取代每次都使用运行缓慢的Reflection。我们在Corillian(我最新的项目)中多处使用了这个方法,我们又想要Reflection的灵活性,同时又想保留接近本地的高速读取。

    1: public DynamicPropertyReader(string sourceType, string propertyName)
    2: {
    3:     if (string.IsNullOrEmpty(sourceType))
    4:     {
    5:         throw new ArgumentNullException("sourceType");
    6:     }
    7:     if (string.IsNullOrEmpty(propertyName))
    8:         throw new ArgumentNullException("propertyName");
    9:     // Attmpt to get the type, and throw a TypeLoadException if not found
   10:     Type type = Type.GetType(sourceType, true);
   11:     _fastPropertyGetter = new FastPropertyGetter(propertyName, type);
   12:     if (_fastPropertyGetter.PropertyType == typeof(string))
   13:     {
   14:         _comparator = StringComparator.Instance;
   15:     }
   16:     else if (typeof(IConvertible).IsAssignableFrom(_fastPropertyGetter.PropertyType) &&
   17:     typeof(IComparable).IsAssignableFrom(_fastPropertyGetter.PropertyType))
   18:     {
   19:         _comparator = NumericComparator.Instance;
   20:     }
   21:     else
   22:     {
   23:         throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture,
   24:         Resources.DoesNotImplementRightInterfaces,
   25:         propertyName, sourceType));
   26:     }
   27: }

它做了什么呢?代码很长,不过很灵活。我觉得它可以更紧凑一些,但基本来说它创建了在运行时根据搜索目标属性的方法。然后getter被缓存并调用来搜索属性。每个属性都会生成一个对应的DynamicMethods。记住DynamicMethod很神奇,它启用了IronPython和DLR。我听说这种技术在不久的将来在使用DLR时将会更为清晰。也许Harry可以在这方面拓展一下。CodeProject上有一篇很好的博文,探讨了这种专门为reflection提速的代码构造方法

    1: public class FastPropertyGetter
    2: {
    3:     private delegate PropertyResult GetData(object data);
    4:     private GetData GDDelegate;
    5:     public Type PropertyType
    6:     {
    7:         get;
    8:         private set;
    9:     }
   10:     public FastPropertyGetter(string propertyName, Type type)
   11:     {
   12:         if (string.IsNullOrEmpty(propertyName))
   13:             throw new ArgumentNullException("propertyName");
   14:         if (null == type)
   15:             throw new ArgumentNullException("type");
   16:         PropertyInfo propertyInfo = type.GetProperty(propertyName);
   17:         if (propertyInfo == null || !propertyInfo.CanRead)
   18:             throw new ArgumentException(
   19:             string.Format(CultureInfo.CurrentCulture, Resources.NoReadableProperty, propertyName, type));
   20:         MethodInfo methodInfo = propertyInfo.GetGetMethod();
   21:         PropertyType = methodInfo.ReturnType;
   22:         DynamicMethod dm = new DynamicMethod("FastGetProperty",
   23:         typeof(PropertyResult),
   24:         new Type[] { typeof(object) }, typeof(FastPropertyGetter));
   25:         ILGenerator il = dm.GetILGenerator();
   26:         il.DeclareLocal(typeof(PropertyResult));
   27:         LocalBuilder lb = il.DeclareLocal(type);
   28:         il.Emit(OpCodes.Newobj, typeof(PropertyResult).GetConstructor(new Type[0] { }));
   29:         il.Emit(OpCodes.Stloc_0);
   30:         il.Emit(OpCodes.Ldarg_0);
   31:         il.Emit(OpCodes.Isinst, type);
   32:         // branch if IS an instance of type
   33:         Label isInstance = il.DefineLabel();
   34:         il.Emit(OpCodes.Brtrue_S, isInstance);
   35:         {
   36:             // load the GetterResult
   37:             il.Emit(OpCodes.Ldloc_0);
   38:             // Create a 'false' bool
   39:             il.Emit(OpCodes.Ldc_I4_0);
   40:             // Set result code to GetterResultCode.ObjectNotExpectedType
   41:             il.Emit(OpCodes.Callvirt, typeof(PropertyResult).GetProperty("ObjectMatched").GetSetMethod());
   42:             // reload the GetterResult and return it
   43:             il.Emit(OpCodes.Ldloc_0);
   44:             il.Emit(OpCodes.Ret);
   45:         }
   46:         // isInstance:
   47:         // object IS expected instance
   48:         il.MarkLabel(isInstance);
   49:         {
   50:             // load the GetterResult
   51:             il.Emit(OpCodes.Ldloc_0);
   52:             // load the passed object
   53:             il.Emit(OpCodes.Ldarg_0);
   54:             if (type.IsValueType)
   55:             {
   56:                 il.Emit(OpCodes.Unbox_Any, type);
   57:                 il.Emit(OpCodes.Stloc_1);
   58:                 il.Emit(OpCodes.Ldloca_S, lb.LocalIndex);
   59:             }
   60:             if (methodInfo.IsFinal)
   61:             {
   62:                 // Call the property get method
   63:                 il.Emit(OpCodes.Call, methodInfo);
   64:             }
   65:             else
   66:             {
   67:                 // Call the property get method
   68:                 il.Emit(OpCodes.Callvirt, methodInfo);
   69:             }
   70:             // box if necessary
   71:             if (methodInfo.ReturnType.IsValueType)
   72:             {
   73:                 il.Emit(OpCodes.Box, methodInfo.ReturnType);
   74:             }
   75:             // Set data to result of query
   76:             il.Emit(OpCodes.Callvirt, typeof(PropertyResult).GetProperty("Data").GetSetMethod());
   77:             // reload the GetterResult and return it
   78:             il.Emit(OpCodes.Ldloc_0);
   79:             il.Emit(OpCodes.Ret);
   80:         }
   81:         GDDelegate = (GetData)dm.CreateDelegate(typeof(GetData), null);
   82:     }
   83:     public PropertyResult GetValue(object data)
   84:     {
   85:         return GDDelegate(data);
   86:     }
   87: }

IIS7的Hostable Web Core

CarlosAg为IIS7的核心hwebcore.dll库写了个很不错的wrapper,能让你在进程中拥有你自己的IIS7实例(IIS7 instance)。它不是一个HttpListener,也不是进程外项目。它是一个完整的IIS7,在你的进程中运行。它十分强大,也非常简单,如下所示:

    1: internal class Program
    2: {
    3:     private static void Main(string[] args)
    4:     {
    5:         int port = 54321;
    6:         int siteId = 1;
    7:         WebServer server = new WebServer(@"d:\Site", port, siteId);
    8:         server.Start();
    9:         Console.WriteLine("Server Started!... Press Enter to Shutdown");
   10:         Console.ReadLine();
   11:         Console.WriteLine("Shutting down");
   12:         server.Stop();
   13:     }
   14: } 

这是代替HttpListener一个不错的选择,比Visual Web Developer/Cassini更强大。Carlos还设想了这样的可能性:

“这个类库使用的另一中场景也许可以包含一个像‘Demo/Trial CD’这样的东西,让你能把应用程序打包在CD/DVD中,当用户将CD/DVD插入他们的电脑中,就能立刻运行或读取你的Web应用程序Demo,从而达到免安装或避免部署应用程序在他们的实体Web服务器中的目的。”

这做起来也不难:

    1: namespace HWCServer
    2: {
    3:     internal class WebServer : IDisposable
    4:     {
    5:         private string _appHostConfigPath;
    6:         private string _rootWebConfigPath;
    7:         public WebServer(string physicalPath, int port, int siteId)
    8:         {
    9:             string appPoolName = "AppPool" + port.ToString();
   10:             _appHostConfigPath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName() + ".config");
   11:             _rootWebConfigPath = Environment.ExpandEnvironmentVariables(@"%windir%\Microsoft.Net\Framework\v2.0.50727\config\web.config");
   12:             string appHostContent = Resources.AppHostAspNet;
   13:             //string appHostContent = Resources.AppHostStaticFiles;
   14:             File.WriteAllText(_appHostConfigPath,
   15:             String.Format(appHostContent,
   16:             port,
   17:             physicalPath,
   18:             @"%windir%\Microsoft.NET\Framework\v2.0.50727",
   19:             siteId,
   20:             appPoolName));
   21:         }
   22:         ~WebServer()
   23:         {
   24:             Dispose(false);
   25:         }
   26:         public void Dispose()
   27:         {
   28:             Dispose(true);
   29:         }
   30:         private void Dispose(bool disposing)
   31:         {
   32:             if (disposing)
   33:             {
   34:                 GC.SuppressFinalize(this);
   35:             }
   36:             Stop();
   37:         }
   38:         public void Start()
   39:         {
   40:             if (!HostableWebCore.IsActivated)
   41:             {
   42:                 HostableWebCore.Activate(_appHostConfigPath, _rootWebConfigPath, Guid.NewGuid().ToString());
   43:             }
   44:         }
   45:         public void Stop()
   46:         {
   47:             if (HostableWebCore.IsActivated)
   48:             {
   49:                 HostableWebCore.Shutdown(false);
   50:             }
   51:         }
   52:         #region Hostable WebCore
   53:         internal static class HostableWebCore
   54:         {
   55:             private static bool _isActivated;
   56:             private delegate int FnWebCoreActivate([In, MarshalAs(UnmanagedType.LPWStr)]string appHostConfig, [In, MarshalAs(UnmanagedType.LPWStr)]string rootWebConfig, [In, MarshalAs(UnmanagedType.LPWStr)]string instanceName);
   57:             private delegate int FnWebCoreShutdown(bool immediate);
   58:             private static FnWebCoreActivate WebCoreActivate;
   59:             private static FnWebCoreShutdown WebCoreShutdown;
   60:             static HostableWebCore()
   61:             {
   62:                 // Load the library and get the function pointers for the WebCore entry points
   63:                 const string HWCPath = @"%windir%\system32\inetsrv\hwebcore.dll";
   64:                 IntPtr hwc = NativeMethods.LoadLibrary(Environment.ExpandEnvironmentVariables(HWCPath));
   65:                 IntPtr procaddr = NativeMethods.GetProcAddress(hwc, "WebCoreActivate");
   66:                 WebCoreActivate = (FnWebCoreActivate)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(FnWebCoreActivate));
   67:                 procaddr = NativeMethods.GetProcAddress(hwc, "WebCoreShutdown");
   68:                 WebCoreShutdown = (FnWebCoreShutdown)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(FnWebCoreShutdown));
   69:             }
   70:             public static bool IsActivated
   71:             {
   72:                 get
   73:                 {
   74:                     return _isActivated;
   75:                 }
   76:             }
   77:             public static void Activate(string appHostConfig, string rootWebConfig, string instanceName)
   78:             {
   79:                 int result = WebCoreActivate(appHostConfig, rootWebConfig, instanceName);
   80:                 if (result != 0)
   81:                 {
   82:                     Marshal.ThrowExceptionForHR(result);
   83:                 }
   84:                 _isActivated = true;
   85:             }
   86:             public static void Shutdown(bool immediate)
   87:             {
   88:                 if (_isActivated)
   89:                 {
   90:                     WebCoreShutdown(immediate);
   91:                     _isActivated = false;
   92:                 }
   93:             }
   94:             private static class NativeMethods
   95:             {
   96:                 [DllImport("kernel32.dll")]
   97:                 internal static extern IntPtr LoadLibrary(String dllname);
   98:                 [DllImport("kernel32.dll")]
   99:                 internal static extern IntPtr GetProcAddress(IntPtr hModule, String procname);
  100:             }
  101:         }
  102:     }
  103: }

多么清晰的wrapper类!希望你乐在其中,继续阅读更多代码,成为更好的Coder!