.net的反射技术(2)深究及 性能比较

来源:互联网 发布:lake软件 编辑:程序博客网 时间:2024/05/29 13:44

FastReflection Library

 老赵编写

全文英文版:FastReflection Library

这是我在CodePlex上创建的一个项目,它的网址是http://www.codeplex.com/FastReflectionLib,使用Microsoft Public License (Ms-PL),您可以随意在自己的产品中使用它的全部或部分代码。这个项目用到了我在《方法的直接调用,反射调用与Lambda表达式调用》和《这下没理由嫌Eval的性能差了吧?》两篇文章里用到的做法,并加以提炼和扩展发布的项目——随便搞搞,留个印记,也供以后参考。

基本使用方式

反射是.NET中非常重要的功能。使用反射来构造对象、调用方法或是访问属性是某些项目中常用的做法之一(例如 ORM框架)。众所周知,与一个成员的直接访问相比,反射调用的性能要低好几个数量级。FastReflectionLib提供了一种简便的方式,使一些常用反射调用的性能得到大幅提高。如下:

using System;
using System.Reflection;
using FastReflectionLib;
 
namespace SimpleConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            PropertyInfo propertyInfo = typeof(string).GetProperty("Length");
            MethodInfo methodInfo = typeof(string).GetMethod("Contains");
 
            string s = "Hello World!";
 
            // get value by normal reflection
            int length1 = (int)propertyInfo.GetValue(s, null);
            // get value by the extension method from FastReflectionLib,
            // which is much faster
            int length2 = (int)propertyInfo.FastGetValue(s);
 
            // invoke by normal reflection
            bool result1 = (bool)methodInfo.Invoke(s, new object[] { "Hello" });
            // invoke by the extension method from FastReflectionLib,
            // which is much faster
            bool result2 = (bool)methodInfo.FastInvoke(s, new object[] { "Hello" });
        }
    }
}

在得到了PropertyInfo或MethodInfo对象之后,我们可以使用GetValue或Invoke方法来访问属性或调用方法。在 FastReflectionLib中为PropertyInfo、MethodInfo等对象定义了对应的扩展方法,于是我们就可以使用这些扩展方法 (从代码上看来,基本上只是在原来的方法之前加上“Fast”)来进行调用,与之前的方法相比,新的扩展方法性能有极大的提高。

直接使用各工作对象

各FastXxx方法实际上是将PropertyInfo等对象作为Key去一个Cache中获取对应的工作对象,然后调用工作对象上对应的方法。因此,直接调用工作对象可以获得更好的性能。各工作对象类型的对应关系如下:

  • PropertyInfo:IPropertyAccessor
  • MethodInfo:IMethodInvoker
  • ConstructorInfo:IConstructorInvoker
  • FieldInfo:IFieldAccessor

我们可以使用FastReflectionCaches.MethodInvokerCache来获取一个IMethodInvoker对象:

static void Execute(MethodInfo methodInfo, object instance, int times)
{ 
    IMethodInvoker invoker = FastReflectionCaches.MethodInvokerCache.Get(methodInfo);
    object[] parameters = new object[0];
    for (int i = 0; i < times; i++)
    {
        invoker.Invoke(instance, parameters);
    }
}

工作对象的默认实现与扩展

在FastReflectionLib中,已经提供了IPropertyAccessor等接口的默认实现。该实现将会构造一颗表达式树(Expression Tree)并将其编译(调用其Compile方法)以获得一个与反射方法签名相同的委托对象。这是一种简单、通用而安全的实现,由于Compile方法使 用了Emit,其性能也较为令人满意(可见下面的性能测试)。但是这并不是性能最高的做法,如果使用Emit生成最优化的代码,其性能甚至会高于方法的直 接调用(例如Dynamic Reflection Library)。如果您想使用更好的实现来替换,则可以自行构造一个工作对象接口的实现,并替换对应的Factory:

public class BetterPropertyAccessor : IPropertyAccessor
{
    public BetterPropertyAccessor(PropertyInfo propertyInfo) { ... }
 
    ...
}
 
public class BetterPropertyAccessorFactory :
    IFastReflectionFactory<PropertyInfo, IPropertyAccessor>
{
    public IPropertyAccessor Create(PropertyInfo key)
    {
        return new BetterPropertyAccessor(key);
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        FastReflectionFactories.PropertyAccessorFactory =
            new BetterPropertyAccessorFactory();
 
        ...
    }
}

缓存的默认实现与扩展

在FastReflectionLib中使用基于System.Collections.Generic.Dictionary类型编写的缓存容器。每次调用FastXxx扩展方法时,类库将从对应的缓存容器中获取工作对象。如果缓存容器中还没有所需的工作对象,那么它就会调用合适的Factory来构造新的工作对象。从下面的性能测试来看,许多时间是消耗在缓存查找上的,如果您有更好的缓存实现,可以使用以下的方法替换默认的缓存的容器:

public class BetterMethodInvokerCache :
    IFastReflectionCache<MethodInfo, IMethodInvoker>
{
    public IMethodInvoker Get(MethodInfo key) { ... }
}
 
class Program
{
    static void Main(string[] args)
    {
        FastReflectionCaches.MethodInvokerCache = 
            new BetterMethodInvokerCache();
 
        ...
    }
}

根据需要自行缓存工作对象

FastReflectionLib中通过PropertyInfo等对象作为Key,对PropertyAccessor等工作对象进行缓存。但 是在某些场景下,您也可以选择合适的方式来自行缓存工作对象。与FastReflectionLib源码同时发布的CustomCache示例网站中包含了一个FastEval扩展,在某些场景下,我们可以使用这个更高效的方法来替换内置的Eval方法。这个示例的特点如下:

  • 使用对象的类型和属性名同时作为缓存的Key获取对应的PropertyAccessor对象
  • 使用PropertyAccessor获取“匿名对象”中的属性值
  • 缓存的作用域为特定页面,而不是整个AppDomain。

性能测试

FastReflectionLib源码中包含了一个性能测试项目,您可以从中看出FastReflectionLib对于反射的性能改进。摘录部分数据如下(测试在我的笔记本上运行,Release编译)。

执行以下方法:

public class Test
{
    public void MethodWithArgs(int a1, string a2) { }
}

进行一百万次调用,结果如下:

调用方式

消耗时间(秒)

方法直接调用

0.0071397

内置反射调用

1.4936181

工作对象调用

0.0468326

Fast方法调用

0.1373712

  • Categories: 实践优化,项目扩展,.Net框架
  • Tags: 性能,Lambda表达式,反射

这下没理由嫌Eval的性能差了吧?

2009-01-08 18:32by 老赵, 13163 visits

Updated:提供思考题解答

好吧,你偏要说Eval性能差

写 ASP.NET中使用Eval是再常见不过的手段了,好像任何一本ASP.NET书里都会描述如何把一个DataTable绑定到一个控件里去,并且通过 Eval来取值的用法。不过在目前的DDD(Domain Driven Design)时代,我们操作的所操作的经常是领域模型对象。我们可以把任何一个实现了IEnumerable的对象作为绑定控件的数据源,并且在绑定控 件中通过Eval来获取字段的值。如下:

protected void Page_Load(object sender, EventArgs e)
{
    List<Comment> comments = GetComments();
    this.rptComments.DataSource = comments;
    this.rptComments.DataBind();
}
 
<asp:Repeater runat="server" ID="rptComments">
    <ItemTemplate>
        Title: <%# Eval("Title") %><br />
        Conent: <%# Eval("Content") %>
    </ItemTemplate>
    <SeparatorTemplate>
        <hr />
    </SeparatorTemplate>
</asp:Repeater>

在这里,Eval对象就会通过反射来获取Title和Content属性的值。于是经常就有人会见到说:“反射,性能多差啊,我可不用!”。在这里我还是对这种追求细枝末节性能的做法持保留态度。当然,在上面的例子里我们的确可以换种写法:

<asp:Repeater runat="server" ID="rptComments">
    <ItemTemplate>
        Title: <%# (Container.DataItem as Comment).Title %><br />
        Conent: <%# (Container.DataItem as Comment).Content %>
    </ItemTemplate>
    <SeparatorTemplate>
        <hr />
    </SeparatorTemplate>
</asp:Repeater>

我们通过Container.DataItem来获取当前遍历过程中的数据对象,将其转换成Comment之后读取它的Title和Content属性。虽然表达式有些长,但似乎也是个不错的解决方法。性能嘛……肯定是有所提高了。

但是,在实际开发过程中,我们并不一定能够如此轻松的将某个特定类型的数据作为数据源,往往需要组合两种对象进行联合显示。例如,我们在显示评论列表时往往还会要显示发表用户的个人信息。由于C# 3.0中已经支持了匿名对象,所以我们可以这样做:

protected void Page_Load(object sender, EventArgs e)
{
    List<Comment> comments = GetComments();
    List<User> users = GetUsers();
 
    this.rptComments.DataSource = from c in comments
                                  from u in users
                                  where c.UserID == u.UserID
                                  order by c.CreateTime
                                  select new
                                  {
                                      Title = c.Title,
                                      Content = c.Content,
                                      NickName = u.NickName
                                  };
    this.rptComments.DataBind();
}

我们通过LINQ级联Comment和User数据集,可以轻松地构造出构造出作为数据源的匿名对象集合(有没有看出LINQ的美妙?)。上面的匿名对象将包含Title,Content和NickName几个公有属性,因此在页面中仍旧使用Eval来获取数据,不提。

不过我几乎可以肯定,又有人要叫了起来:“LINQ没有用!我们不用LINQ!Eval性能差!我们不用Eval!”。好吧,那么我免为其难地为他们用“最踏实”的技术重新实现一遍:

private Dictionary<int, User> m_users;
protected User GetUser(int userId)
{
    return this.m_users[userId];
}
 
protected void Page_Load(object sender, EventArgs e)
{
    List<Comment> comments = GetComments();
    List<User> users = GetUsers();
 
    this.m_users = new Dictionary<int, User>();
    foreach (User u in users)
    {
        this.m_users[u.UserID] = u;
    }
 
    this.rptComments.DataSource = comments;
    this.rptComments.DataBind();
}
 
<asp:Repeater runat="server" ID="rptComments">
    <ItemTemplate>
        Title: <%# (Container.DataItem as Comment).Title %><br />
        Conent: <%# (Container.DataItem as Comment).Content %><br />
        NickName: <%# this.GetUser((Container.DataItem as Comment).UserID).NickName %>
    </ItemTemplate>
    <SeparatorTemplate>
        <hr />
    </SeparatorTemplate>
</asp:Repeater>

兄弟们自己做判断吧。

嫌反射性能差?算有那么一点道理吧……

反射速度慢?我同意它是相对慢一些。

反射占CPU多?我同意他是相对多一点。

所以Eval不该使用?我不同意——怎能把孩子和脏水一起倒了?我们把反射访问属性的性能问题解决不就行了吗?

性能差的原因在于Eval使用了反射,解决这类问题的传统方法是使用Emit。但是.NET 3.5中现在已经有了Lambda Expression,我们动态构造一个Lambda Expression之后可以通过它的Compile方法来获得一个委托实例,至于Emit实现中的各种细节已经由.NET框架实现了——这一切还真没有 太大难度了。

public class DynamicPropertyAccessor
{
    private Func<object, object> m_getter;
 
    public DynamicPropertyAccessor(Type type, string propertyName)
        : this(type.GetProperty(propertyName))
    { }
 
    public DynamicPropertyAccessor(PropertyInfo propertyInfo)
    {
        // target: (object)((({TargetType})instance).{Property})
 
        // preparing parameter, object type
        ParameterExpression instance = Expression.Parameter(
            typeof(object), "instance");
 
        // ({TargetType})instance
        Expression instanceCast = Expression.Convert(
            instance, propertyInfo.ReflectedType);
 
        // (({TargetType})instance).{Property}
        Expression propertyAccess = Expression.Property(
            instanceCast, propertyInfo);
 
        // (object)((({TargetType})instance).{Property})
        UnaryExpression castPropertyValue = Expression.Convert(
            propertyAccess, typeof(object));
 
        // Lambda expression
        Expression<Func<object, object>> lambda = 
            Expression.Lambda<Func<object, object>>(
                castPropertyValue, instance);
 
        this.m_getter = lambda.Compile();
    }
 
    public object GetValue(object o)
    {
        return this.m_getter(o);
    }
}

在DynamicPropertyAccessor中,我们为一个特定的属性构造一个形为o => object((Class)o).Property的Lambda表达式,它可以被Compile为一个Func<object, object>委托。最终我们可以通过为GetValue方法传入一个Class类型的对象来获取那个指定属性的值。

这个方法是不是比较眼熟?没错,我在《方法的直接调用,反射调用与……Lambda表达式调用》一文中也使用了类似的做法。

测试一下性能?

我们来比对一下属性的直接获取值,反射获取值与……Lambda表达式获取值三种方式之间的性能。

var t = new Temp { Value = null };
 
PropertyInfo propertyInfo = t.GetType().GetProperty("Value");
Stopwatch watch1 = new Stopwatch();
watch1.Start();
for (var i = 0; i < 1000000; i ++)
{
    var value = propertyInfo.GetValue(t, null);
}
watch1.Stop();
Console.WriteLine("Reflection: " + watch1.Elapsed);
 
DynamicPropertyAccessor property = new DynamicPropertyAccessor(t.GetType(), "Value");
Stopwatch watch2 = new Stopwatch();
watch2.Start();
for (var i = 0; i < 1000000; i++)
{
    var value = property.GetValue(t);
}
watch2.Stop();
Console.WriteLine("Lambda: " + watch2.Elapsed);
 
Stopwatch watch3 = new Stopwatch();
watch3.Start();
for (var i = 0; i < 1000000; i++)
{
    var value = t.Value;
}
watch3.Stop();
Console.WriteLine("Direct: " + watch3.Elapsed);

结果如下:

Reflection: 00:00:04.2695397
Lambda: 00:00:00.0445277
Direct: 00:00:00.0175414

使用了DynamicPropertyAccessor之后,性能虽比直接调用略慢,也已经有百倍的差距了。更值得一提的是,DynamicPropertyAccessor还支持对于匿名对象的属性的取值。这意味着,我们的Eval方法完全可以依托在 DynamicPropertyAccessor之上。

离快速Eval只有一步之遥了

“一步之遥”?没错,那就是缓存。调用一个DynamicPropertyAccessor的GetValue方法很省时,可是构造一个 DynamicPropertyAccessor对象却非常耗时。因此我们需要对DynamicPropertyAccessor对象进行缓存,如下:

public class DynamicPropertyAccessorCache
{
    private object m_mutex = new object();
    private Dictionary<Type, Dictionary<string, DynamicPropertyAccessor>> m_cache =
        new Dictionary<Type, Dictionary<string, DynamicPropertyAccessor>>();
 
    public DynamicPropertyAccessor GetAccessor(Type type, string propertyName)
    {
        DynamicPropertyAccessor accessor;
        Dictionary<string, DynamicPropertyAccessor> typeCache;
 
        if (this.m_cache.TryGetValue(type, out typeCache))
        {
            if (typeCache.TryGetValue(propertyName, out accessor))
            {
                return accessor;
            }
        }
 
        lock (m_mutex)
        {
            if (!this.m_cache.ContainsKey(type))
            {
                this.m_cache[type] = new Dictionary<string, DynamicPropertyAccessor>();
            }
 
            accessor = new DynamicPropertyAccessor(type, propertyName);
            this.m_cache[type][propertyName] = accessor;
 
            return accessor;
        }
    }
}

经过测试之后发现,由于每次都要从缓存中获取DynamicPropertyAccessor对象,调用性能有所下降,但是依旧比反射调用要快几十上百倍。

FastEval——还有人会拒绝吗?

FastEval方法,如果在之前的.NET版本中,我们可以将其定义在每个页面的共同基类里。不过既然我们在用.NET 3.5,我们可以使用Extension Method这种没有任何侵入的方式来实现:

public static class FastEvalExtensions
{
    private static DynamicPropertyAccessorCache s_cache = 
        new DynamicPropertyAccessorCache();
 
    public static object FastEval(this Control control, object o, string propertyName)
    {
        return s_cache.GetAccessor(o.GetType(), propertyName).GetValue(o);
    }
 
    public static object FastEval(this TemplateControl control, string propertyName)
    {
        return control.FastEval(control.Page.GetDataItem(), propertyName);
    }
}

我们在Control上的扩展,确保了每个页面中都可以直接通过一个对象和属性名获取一个值。而在TemplateControl上的扩展,则使得 各类可以绑定控件或页面(Page,MasterPage,UserControl)都可以直接通过属性名来获取当前正在绑定的那个数据对象里的属性值。

现在,您还有什么理由拒绝FastEval?

其他

其实我们整篇文章都小看了Eval方法的作用。Eval方法的字符串参数名为“expression”,也就是表达式。事实上我们甚至可以使用 “.”来分割字符串以获取一个对象深层次的属性,例如<%# Eval("Content.Length") %>。那么我们的FastEval可以做到这一点吗?当然可以——只不过这需要您自己来实现了。:)

最后再留一个问题供大家思考:现在DynamicPropertyAccessor只提供一个GetValue方法,那么您能否为其添加一个SetValue方法来设置这个属性呢?希望大家踊跃回复,稍后我将提供我的做法。

思考题解答

有一点大家应该知道,一个属性其实是由一对get/set方法组成(当然可能缺少其中一个)。而获取了一个属性的PropertyInfo对象之后,可以通过它的GetSetMethod方法来获取它的设置方法。接下来的工作,不就可以完全交给《方法的直接调用,反射调用与……Lambda表达式调用》一文里的DynamicMethodExecutor了吗?因此为DynamicPropertyAccessor添加一个SetValue方法也很简单:

public class DynamicPropertyAccessor
{
    ...
    private DynamicMethodExecutor m_dynamicSetter;
 
    ...
 
    public DynamicPropertyAccessor(PropertyInfo propertyInfo)
    {
        ...
 
        MethodInfo setMethod = propertyInfo.GetSetMethod();
        if (setMethod != null)
        {
            this.m_dynamicSetter = new DynamicMethodExecutor(setMethod);
        }
    }
 
    ...
 
    public void SetValue(object o, object value)
    {
        if (this.m_dynamicSetter == null)
        {
            throw new NotSupportedException("Cannot set the property.");
        }
 
        this.m_dynamicSetter.Execute(o, new object[] { value });
    }
}

在下面的评论中,SuchCloud已经想到了类似的做法,值得鼓励,同时多谢支持。

  • Categories: .Net框架,ASP.NET,实践优化
  • Tags: LINQ, 性能, Eval

方法的直接调用,反射调用与……Lambda表达式调用

2008-11-24 01:59by 老赵, 27054 visits

想调用一个方法很容易,直接代码调用就行,这人人都会。其次呢,还可以使用反射。不过通过反射调用的性能会远远低于直接调用——至少从绝对时间上来看的确是这样。虽然这是个众所周知的现象,我们还是来写个程序来验证一下。比如我们现在新建一个Console应用程序,编写一个最简单的Call方法。

class Program
{
    static void Main(string[] args)
    {
        
    }
 
    public void Call(object o1, object o2, object o3) { }
}

Call方法接受三个object参数却没有任何实现,这样我们就可以让测试专注于方法调用,而并非方法实现本身。于是我们开始编写测试代码,比较一下方法的直接调用与反射调用的性能差距:

static void Main(string[] args)
{
    int times = 1000000;
    Program program = new Program();
    object[] parameters = new object[] { new object(), new object(), new object() };
    program.Call(null, null, null); // force JIT-compile
 
    Stopwatch watch1 = new Stopwatch();
    watch1.Start();
    for (int i = 0; i < times; i++)
    {
        program.Call(parameters[0], parameters[1], parameters[2]);
    }
    watch1.Stop();
    Console.WriteLine(watch1.Elapsed + " (Directly invoke)");
 
    MethodInfo methodInfo = typeof(Program).GetMethod("Call");
    Stopwatch watch2 = new Stopwatch();
    watch2.Start();
    for (int i = 0; i < times; i++)
    {
        methodInfo.Invoke(program, parameters);
    }
    watch2.Stop();
    Console.WriteLine(watch2.Elapsed + " (Reflection invoke)");
 
    Console.WriteLine("Press any key to continue...");
    Console.ReadKey();
}

执行结果如下:

00:00:00.0119041 (Directly invoke)
00:00:04.5527141 (Reflection invoke)
Press any key to continue...

通过各调用一百万次所花时间来看,两者在性能上具有数量级的差距。因此,很多框架在必须利用到反射的场景中,都会设法使用一些较高级的替代方案来改 善性能。例如,使用CodeDom生成代码并动态编译,或者使用Emit来直接编写IL。不过自从.NET 3.5发布了Expression相关的新特性,我们在以上的情况下又有了更方便并直观的解决方案。

了解Expression相关特性的朋友可能知 道,System.Linq.Expressions.Expression<TDelegate>类型的对象在调用了它了Compile方 法之后将得到一个TDelegate类型的委托对象,而调用一个委托对象与直接调用一个方法的性能开销相差无几。那么对于上面的情况,我们又该得到什么样的Delegate对象呢?为了使解决方案足够通用,我们必须将各种签名的方法统一至同样的委托类型中,如下:

public Func<object, object[], object> GetVoidDelegate()
{
    Expression<Action<object, object[]>> exp = (instance, parameters) => 
        ((Program)instance).Call(parameters[0], parameters[1], parameters[2]);
 
    Action<object, object[]> action = exp.Compile();
    return (instance, parameters) =>
    {
        action(instance, parameters);
        return null;
    };
}

如上,我们就得到了一个Func<object, object[], object>类型的委托,这意味它接受一个object类型与object[]类型的参数,以及返回一个object类型的结果——等等,朋友们 有没有发现,这个签名与MethodInfo类型的Invoke方法完全一致?不过可喜可贺的是,我们现在调用这个委托的性能远高于通过反射来调用了。那么对于有返回值的方法呢?那构造一个委托对象就更方便了:

public int Call(object o1, object o2) { return 0; }
 
public Func<object, object[], object> GetDelegate()
{
    Expression<Func<object, object[], object>> exp = (instance, parameters) =>
        ((Program)instance).Call(parameters[0], parameters[1]);
 
    return exp.Compile();
}

至此,我想朋友们也已经能够轻松得出调用静态方法的委托构造方式了。可见,这个解决方案的关键在于构造一个合适的Expression<TDelegate>,那么我们现在就来编写一个DynamicExecuter类来作为一个较为完整的解决方案:

public class DynamicMethodExecutor
{
    private Func<object, object[], object> m_execute;
 
    public DynamicMethodExecutor(MethodInfo methodInfo)
    {
        this.m_execute = this.GetExecuteDelegate(methodInfo);
    }
 
    public object Execute(object instance, object[] parameters)
    {
        return this.m_execute(instance, parameters);
    }
 
    private Func<object, object[], object> GetExecuteDelegate(MethodInfo methodInfo)
    {
        // parameters to execute
        ParameterExpression instanceParameter = 
            Expression.Parameter(typeof(object), "instance");
        ParameterExpression parametersParameter = 
            Expression.Parameter(typeof(object[]), "parameters");
 
        // build parameter list
        List<Expression> parameterExpressions = new List<Expression>();
        ParameterInfo[] paramInfos = methodInfo.GetParameters();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            // (Ti)parameters[i]
            BinaryExpression valueObj = Expression.ArrayIndex(
                parametersParameter, Expression.Constant(i));
            UnaryExpression valueCast = Expression.Convert(
                valueObj, paramInfos[i].ParameterType);
 
            parameterExpressions.Add(valueCast);
        }
 
        // non-instance for static method, or ((TInstance)instance)
        Expression instanceCast = methodInfo.IsStatic ? null : 
            Expression.Convert(instanceParameter, methodInfo.ReflectedType);
 
        // static invoke or ((TInstance)instance).Method
        MethodCallExpression methodCall = Expression.Call(
            instanceCast, methodInfo, parameterExpressions);
        
        // ((TInstance)instance).Method((T0)parameters[0], (T1)parameters[1], ...)
        if (methodCall.Type == typeof(void))
        {
            Expression<Action<object, object[]>> lambda = 
                Expression.Lambda<Action<object, object[]>>(
                    methodCall, instanceParameter, parametersParameter);
 
            Action<object, object[]> execute = lambda.Compile();
            return (instance, parameters) =>
            {
                execute(instance, parameters);
                return null;
            };
        }
        else
        {
            UnaryExpression castMethodCall = Expression.Convert(
                methodCall, typeof(object));
            Expression<Func<object, object[], object>> lambda = 
                Expression.Lambda<Func<object, object[], object>>(
                    castMethodCall, instanceParameter, parametersParameter);
 
            return lambda.Compile();
        }
    }
}

DynamicMethodExecutor的关键就在于GetExecuteDelegate方法中构造Expression Tree的逻辑。如果您对于一个Expression Tree的结构不太了解的话,不妨尝试一下使用ExpressionTree Visualizer来 对一个现成的Expression Tree进行观察和分析。我们将一个MethodInfo对象传入DynamicMethodExecutor的构造函数之后,就能将各组不同的实例对象和参数对象数组传入Execute进行执行。这一切就像使用反射来进行调用一般,不过它的性能就有了明显的提高。例如我们添加更多的测试代码:

DynamicMethodExecutor executor = new DynamicMethodExecutor(methodInfo);
Stopwatch watch3 = new Stopwatch();
watch3.Start();
for (int i = 0; i < times; i++)
{
    executor.Execute(program, parameters);
}
watch3.Stop();
Console.WriteLine(watch3.Elapsed + " (Dynamic executor)");

现在的执行结果则是:

00:00:00.0125539 (Directly invoke)
00:00:04.5349626 (Reflection invoke)
00:00:00.0322555 (Dynamic executor)
Press any key to continue...

事实上,Expression<TDelegate>类型的Compile方法正是使用Emit来生成委托对象。不过现在我们已经无需 将目光放在更低端的IL上,只要使用高端的API来进行Expression Tree的构造,这无疑是一种进步。不过这种方法也有一定局限性,例如我们只能对公有方法进行调用,并且包含out/ref参数的方法,或者除了方法外的 其他类型成员,我们就无法如上例般惬意地编写代码了。

补充

木野狐兄在评论中引用了Code Project的文章《A General FastMethod Invoker》, 其中通过Emit构建了FastInvokeHandler委托对象(其签名与Func<object,object[], object>完全相同)的调用效率似乎较“方法直接”调用的性能更高(虽然从原文示例看来并非如此)。事实上FastInvokeHandler其内部实现与DynamicMethodExecutor完全相同,居然有如此令人不可思议的表现实在让人啧啧称奇。我猜测,FastInvokeHandler与DynamicMethodExecutor的性能优势可能体现在以下几个方面:

  1. 范型委托类型的执行性能较非范型委托类型略低(求证)。
  2. 多了一次Execute方法调用,损失部分性能。
  3. 生成的IL代码更为短小紧凑。
  4. 木野狐兄没有使用Release模式编译。:P

不知道是否有对此感兴趣的朋友能够再做一个测试,不过请注意此类性能测试一定需要在Release编译下进行(这点很容易被忽视),否则意义其实不大。

此外,我还想强调的就是,本篇文章进行是纯技术上的比较,并非在引导大家追求点滴性能上的优化。有时候看到一些关于比较for或foreach性能优劣的文章让许多朋友都纠结与此,甚至搞得面红耳赤,我总会觉得有些无可奈何。其实从理论上来说,提高性能的方式有许许多多,记得当时在大学里学习 Introduction to Computer System这门课时得一个作业就是为一段C程序作性能优化,当时用到不少手段,例如内联方法调用以减少CPU指令调用次数、调整循环嵌套顺序以提高 CPU缓存命中率,将一些代码使用内嵌ASM替换等等,可谓“无所不用其极”,大家都在为几个时钟周期的性能提高而发奋图强欢呼雀跃……

那是理论,是在学习。但是在实际运用中,我们还必须正确对待学到的理论知识。我经常说的一句话是:“任何应用程序都会有其性能瓶颈,只有从性能瓶颈着手才能做到事半功倍的结果。”例如,普通Web应用的性能瓶颈往往在外部IO(尤其是数据库读写),要真正提高性能必须从此入手(例如数据库调优,更好 的缓存设计)。正因如此,开发一个高性能的Web应用程序的关键不会在语言或语言运行环境上,.NET、RoR、PHP、Java等等在这一领域都表现良好。

This article

  • Categories: 实践优化,.Net框架
  • Tags: 性能,Lambda表达式,反射,Emit

 

附录:

A General Fast Method Invoker

By Luyan| 4 Jul 2006

Method reflecting invoke is nice, but veryfrequently it can be too slow. This article describes an alternative method fordynamic method invoke.

PrintArticle

Twitter

Digg

Facebook

Del.icio.us

Reddit

Stumbleupon

Newsvine

Technorati

Mr. Wong

Yahoo!

Google

Windows Live

Send as Email

Add to your CodeProject bookmarks

Discussthis article

66

Reportthis article as inappropriate

 

Article

Browse Code

Stats

Revisions

 

  4.84 (69 votes)

     

 


1


2


3


4


5

Sponsored Links

 

See Also

·        Articleslike this

·        Articlesby this author

  • Download source and performance test project - 4.04 Kb

Introduction

Sometimes, I run across the need to dynamically invoke themethod of an object, where the actual method might not be known until run-time.Usually, Reflecting is nice, but frequently doing it can be too slow. Thisarticle describes an alternative method for dynamic method invocation.

Background

When I read the article FastDynamic Property Accessors, I was thinking about my project, it has a lotsof reflecting methods in circle. But it's methods not properties. But theDynamicMethodreminded me, maybe I could useEmit to generate aDynamicMethodto bind a special method before it can be invoked. I hope it will improveperformance.

Using the Code

First, I reflected out the method which will be invoked:

Collapse

MethodInfo methodInfo = typeof(Person).GetMethod("Say");

Then, I get the MethodInvoker to invoke:

Collapse

FastInvokeHandler fastInvoker = GetMethodInvoker(methodInfo);
fastInvoker(new Person(), new object[]{"hello"});

Instead of using reflection method, invoke in the past:

Collapse

methodInfo.Invoke(new Person(), new object[]{"hello"});

Implementation

First, I need to define a delegate to adapt the dynamicmethod:

Collapse

public delegate object FastInvokeHandler(object target, 
                                   object[] paramters);

It looks the same as the class MethodInfo's Invokemethod. Yes, that means I can write the same code to use it like in the past.

This code generates the DynamicMethod:

Collapse

public static FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)
{
    DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, 
                     typeof(object), new Type[] { typeof(object), 
                     typeof(object[]) }, 
                     methodInfo.DeclaringType.Module);
    ILGenerator il = dynamicMethod.GetILGenerator();
    ParameterInfo[] ps = methodInfo.GetParameters();
    Type[] paramTypes = new Type[ps.Length];
    for (int i = 0; i < paramTypes.Length; i++)
    {
        paramTypes[i] = ps[i].ParameterType;
    }
    LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
    for (int i = 0; i < paramTypes.Length; i++)
    {
        locals[i] = il.DeclareLocal(paramTypes[i]);
    }
    for (int i = 0; i < paramTypes.Length; i++)
    {
        il.Emit(OpCodes.Ldarg_1);
        EmitFastInt(il, i);
        il.Emit(OpCodes.Ldelem_Ref);
        EmitCastToReference(il, paramTypes[i]);
        il.Emit(OpCodes.Stloc, locals[i]);
    }
    il.Emit(OpCodes.Ldarg_0);
    for (int i = 0; i < paramTypes.Length; i++)
    {
        il.Emit(OpCodes.Ldloc, locals[i]);
    }
    il.EmitCall(OpCodes.Call, methodInfo, null);
    if (methodInfo.ReturnType == typeof(void))
        il.Emit(OpCodes.Ldnull);
    else
        EmitBoxIfNeeded(il, methodInfo.ReturnType);
    il.Emit(OpCodes.Ret);
    FastInvokeHandler invoder = 
      (FastInvokeHandler)dynamicMethod.CreateDelegate(
      typeof(FastInvokeHandler));
    return invoder;
}

Conclusion

Well, I think this is a general way that can be usedinstead of most of the reflection methods to get about 50 times performanceimprovement. Any suggestions for improvements are welcome.

Extra advantage (reminded by MaxGuernsey): If an exceptionoccurs in your code,FastInovker would throw the original one, buttheMethod.Invoke would throw aTargetInvocationException.

History

  • 2006-7-05: Updated to add static method support. Thanks Manuel Abadia.
  • 2006-6-30: Updated to add ref/out parameter support. Thanks Roger for his nice suggestion.

License

This article has no explicit license attached to it but maycontain usage terms in the article text or the download files themselves. If indoubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Luyan



China

Member

I am currently working for a .NET framework names AgileFramework. It's introduction at here:
http://www.agilelabs.cn/agileframework

Now I'm living in China. I have been designing and developing .NET based software applications for 5+ years.

Attribute操作的性能优化方式

2009-11-18 02:09by 老赵, 16720 visits

Attribute是.NET平台上提供的一种元编程能力,可以通过标记的方式来修饰各种成员。无论是组件设计,语言之间互通,还是最普通的框架使用,现在已经都离不开Attribute了。迫于Attribute的功能的重要性(Kent Beck认为NUnit比早期JUnit设计的好,一个主要方面便是利用了Attribute),Java语言也在5.0版本中引入了与 Attribute类似的Annotation概念。不过Attribute说到底也是一种反射操作,平时正常使用不会带来问题,但是密集的调用还是对性 能有一定影响的。这次我们就来总结看看我们究竟可以如何回避Attribute操作的一些性能问题。

假设我们有一个Attribute,它定义在一个类型上:

[AttributeUsage(AttributeTargets.Class,
    AllowMultiple = true,
    Inherited = true)]
public class TestAttribute : Attribute
{
    public TestAttribute(string prop)
    {
        this.Prop = prop;
    }
 
    public TestAttribute() { }
 
    public string Prop { get; set; }
}
 
[Test("Hello World")]
[Test(Prop = "Hello World")]
public class SomeClass { }

那么,如果我们需要获得SomeClass类型上所标记的TestAttribute,我们一般会使用Type对象的GetCustomAttributes方法。那么在其中又发生了什么呢?

通过.NET Reflector来追踪其中实现,会发现这些逻辑最终是由CustomAttribute的GetCustomAttributes方法完成的,感兴趣 的朋友们可以找到那个最复杂的重载。由于实现有些复杂,我没有看懂完整的逻辑,但从关键的代码上可以看出,它其实是使用了Activator.CreateInstance方法创建对象,并且使用反射对Attribute对象的属性进行设置。于是我便打算了解一下这些反射操作占整个GetCustomAttributes方法的多大比重:

CodeTimer.Time("GetCustomAttributes", 1000 * 100, () =>
{
    var attributes = typeof(SomeClass).GetCustomAttributes(typeof(TestAttribute), true);
});
 
CodeTimer.Time("Reflection", 1000 * 100, () =>
{
    var a1 = (TestAttribute)Activator.CreateInstance(typeof(TestAttribute), "Hello World");
    var a2 = (TestAttribute)Activator.CreateInstance(typeof(TestAttribute));
    typeof(TestAttribute).GetProperty("Prop").SetValue(a2, "Hello World", null);
});

结果如下:

GetCustomAttributes
        Time Elapsed:   2,091ms
        CPU Cycles:     5,032,765,488
        Gen 0:          43
        Gen 1:          0
        Gen 2:          0
 
Reflection
        Time Elapsed:   527ms
        CPU Cycles:     1,269,399,624
        Gen 0:          40
        Gen 1:          0
        Gen 2:          0

可以看出,虽然GetCustomAttributes方法中使用了反射进行对象的创建和属性设置,但是它的大部分开销还是用于获取一些元数据的,它们占据了3/4的时间,而反射的开销其实只占了1/4左右。这就有些令人奇怪了,既然是静态的元数据,为什么.NET Framework不对这些数据进行缓存,而是每次再去取一次呢?即便是我们不应该缓存最后得到的Attribute对象,但是用于构造对象的“信息”是完全可以缓存下来的。

事实上,经由上次heros同学指出,.NET Framework事实上已经给出了足够的信息,那便是CustomAttributeData的 GetCustomAttributes方法,它返回的是IList<CustomAttributeData>对象,其中包含了构造 Attribute所需要的全部信息。换句话说,我完全可以根据一个CustomAttributeData来“快速构建”Attribute对象:

public class AttributeFactory
{
    public AttributeFactory(CustomAttributeData data)
    {
        this.Data = data;
 
        var ctorInvoker = new ConstructorInvoker(data.Constructor);
        var ctorArgs = data.ConstructorArguments.Select(a => a.Value).ToArray();
        this.m_attributeCreator = () => ctorInvoker.Invoke(ctorArgs);
 
        this.m_propertySetters = new List<Action<object>>();
        foreach (var arg in data.NamedArguments)
        {
            var property = (PropertyInfo)arg.MemberInfo;
            var propertyAccessor = new PropertyAccessor(property);
            var value = arg.TypedValue.Value;
            this.m_propertySetters.Add(o => propertyAccessor.SetValue(o, value));
        }
    }
 
    public CustomAttributeData Data { get; private set; }
 
    private Func<object> m_attributeCreator;
    private List<Action<object>> m_propertySetters;
 
    public Attribute Create()
    {
        var attribute = this.m_attributeCreator();
 
        foreach (var setter in this.m_propertySetters)
        {
            setter(attribute);
        }
 
        return (Attribute)attribute;
    }
}

AttributeFactory利用了FastReflectionLib,将ConstructorInfo和PropertyInfo封装成性能很高的ConstructorInvoker和PropertyAccessor对象,这样使用起来便有数量级的性能提高。我们再来进行一番测试:

var factories = CustomAttributeData.GetCustomAttributes(typeof(SomeClass))
    .Where(d => d.Constructor.DeclaringType == typeof(TestAttribute))
    .Select(d => new AttributeFactory(d)).ToList();
 
CodeTimer.Time("GetCustomAttributes", 1000 * 100, () =>
{
    var attributes = typeof(SomeClass).GetCustomAttributes(typeof(TestAttribute), true);
});
 
CodeTimer.Time("AttributeFactory", 1000 * 100, () => factories.ForEach(f => f.Create()));

结果如下:

GetCustomAttributes
        Time Elapsed:   2,131ms
        CPU Cycles:     5,136,848,904
        Gen 0:          43
        Gen 1:          43
        Gen 2:          0
 
Attribute Factory
        Time Elapsed:   18ms
        CPU Cycles:     44,235,564
        Gen 0:          4
        Gen 1:          4
        Gen 2:          0

在这里,我们先获得SomeClass中所有定义过的CustomAttributeData对象,然后根据其Constructor的类型来判断 哪些是用于构造TestAttribute对象的,然后用它们来构造AttributeFactory。在实际使用过程 中,AttributeFactory实例可以缓存下来,并反复使用。这样的话,我们即可以每次得到新的Attribute对象,又可以避免 GetCustomAttributes方法所带来的莫名其妙的开销。

事实上,我们完全可以利用这个方法,来实现一个性能更高的GetCustomAttributesEx方法,它的行为可以和.NET自带的 GetCustomAttributes完全一致,但是性能可以快上无数——可能是100倍。不过,这个方法虽然不难编写,但比较麻烦。因为 CustomAttributeData只能用于获得“直接定义”在某个成员上的数据,而实际情况是,我们往往还必须根据某个Attribute上标记的 AttributeUsage的AllowMultiple和Inherited属性来决定是否要遍历整个继承链。只有这般,我们才能百分之百地重现GetCustomAttribute方法的行为。

不过我们在这里有个优势,那便是“静态”。一旦“静态”,我们便可以为某个特定的场景,用“肉眼”判断出特定的处理方式,这样便不需要一个非常通用 的GetCustomAttributeEx方法了。例如在实际使用过程中,我们可以可以发现某个Attribute的Inherited属性为 false,那么我们便可以免去遍历继承链的麻烦。

最后还有两点可能值得一提:

除了Type,Assembly等成员自带的GetCustomAttributes方法之外,Attribute类也有些静态 GetCustomAttributes方法可用于获取Attribute对象。但是,通过.NET Reflector,我们可以发现,Attribute类中的静态方法,最终还是委托给各自的实例方法,因此不会有性能提高。唯一区别对待的是 ParameterInfo——不过我没搞懂为什么那么复杂,感兴趣的朋友可以自行探索一番。

如果仅仅是判断一个成员是否定义了某个特定类型的Attribute对象,那么可以使用Attribute.IsDefined静态方法。它的性能 比GetCustomAttributes后再判断数组的Length要高效许多倍。不过个人认为这点倒并不是非常重要,因为这原本就是个静态的信息,即 便是我们使用较慢的GetCustomAttributes方法来进行判断,也可以把最终的true或false结果进行缓存,这自然也不会有性能问题 了。

我们之所以要反复调用GetCustomAttributes方法,就是因为每次得到的Attribute对象都是新建的,因此在某些场景下可能无法缓存它们。不过现在已经有了现在更快的做法,在这方面自然也就不会有太大问题了。

  • Categories: .Net框架,实践优化
  • Tags: 性能,FastReflectionLib,自定义属性

一个简单的性能计数器:CodeTimer

2009-03-10 01:03by 老赵, 28427 visits

有数据,有真相,相信大家在平时的工作或学习过程中,都需要比较几种不同方法或实现之间的性能差距。在这些时候,往往就需要我们不 断地创建Stopwatch,打开,关闭,然后打印时间。这种一遍又一遍的重复终有一天会让人忍无可忍,因此如果能有一个“标准”的性能计数器,那应该可 以让生活轻松许多。这个性能计数器不用复杂,够用就好;也不需要考虑扩展性,要扩展时直接修改代码就够了;同样不需要考虑输出格式,直接打印在 Console就行。

在上次的.NET技术大会中,Jeffrey Richter大叔在Keynote Session中进行了一个名为“The Performance ofEveryday Things”的主题演讲,展示了各种常用编程元素之间的性能对比。在演示中他使用了一个名为CodeTimer的简单计数器,用于统计每种做法的性能。可惜翻遍了每个地方都没发现JR大叔在哪里公开了这个计数器的实现。算了,那么就凭着印象写一个出来吧,反正也不复杂。

总的来说,CodeTimer有两个公开方法,一个是Initialize,一个是Time:

public static class CodeTimer
{
    public static void Initialize()
    {
        Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
        Thread.CurrentThread.Priority = ThreadPriority.Highest;
        Time("", 1, () => { });
    }
 
    public static void Time(string name, int iteration, Action action)
    {
        ...
    }
}

CodeTimer.Initialize方法应该在测试开始前调用。首先它会把当前进程及当前线程的优先级设为最高,这样便可以相对减少操作系统在调度上造成的干扰。然后调用一次Time方法进行“预热”,让JIT将IL编译成本地代码,让Time方法尽快“进入状态”。Time方法则是真正用于 性能计数的方法,实现如下:

public static void Time(string name, int iteration, Action action)
{
    if (String.IsNullOrEmpty(name)) return;
 
    // 1.
    ConsoleColor currentForeColor = Console.ForegroundColor;
    Console.ForegroundColor = ConsoleColor.Yellow;
    Console.WriteLine(name);
 
    // 2.
    GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
    int[] gcCounts = new int[GC.MaxGeneration + 1];
    for (int i = 0; i <= GC.MaxGeneration; i++)
    {
        gcCounts[i] = GC.CollectionCount(i);
    }
 
    // 3.
    Stopwatch watch = new Stopwatch();
    watch.Start();
    ulong cycleCount = GetCycleCount();
    for (int i = 0; i < iteration; i++) action();
    ulong cpuCycles = GetCycleCount() - cycleCount;
    watch.Stop();
 
    // 4.
    Console.ForegroundColor = currentForeColor;
    Console.WriteLine("\tTime Elapsed:\t" + watch.ElapsedMilliseconds.ToString("N0") + "ms");
    Console.WriteLine("\tCPU Cycles:\t" + cpuCycles.ToString("N0"));
 
    // 5.
    for (int i = 0; i <= GC.MaxGeneration; i++)
    {
        int count = GC.CollectionCount(i) - gcCounts[i];
        Console.WriteLine("\tGen " + i + ": \t\t" + count);
    }
 
    Console.WriteLine();
}
 
private static ulong GetCycleCount()
{
    ulong cycleCount = 0;
    QueryThreadCycleTime(GetCurrentThread(), ref cycleCount);
    return cycleCount;
}
 
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool QueryThreadCycleTime(IntPtr threadHandle, ref ulong cycleTime);
 
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentThread();

Time方法接受三个参数,名称,循环次数以及需要执行的方法体。打印出花费时间,消耗的CPU时钟周期,以及各代垃圾收集的回收次数。具体实现分几个步骤,如下:

  1. 保留当前控制台前景色,并使用黄色输出名称参数。
  2. 强制GC进行收集,并记录目前各代已经收集的次数。
  3. 执行代码,记录下消耗的时间及CPU时钟周期1
  4. 恢复控制台默认前景色,并打印出消耗时间及CPU时钟周期。
  5. 打印执行过程中各代垃圾收集回收次数。

与传统计数方法相比,这段代码还输出了更多信息:CPU时钟周期及各代垃圾收集回收次数。CPU时钟周期是性能计数中的辅助参考,说明CPU分配了 多少时间片给这段方法来执行,它和消耗时间并没有必然联系。例如Thread.Sleep方法会让CPU暂时停止对当前线程的“供给”,这样虽然消耗了时 间,但是节省了CPU时钟周期:

CodeTimer.Time("Thread Sleep", 1, () => { Thread.Sleep(3000); });
CodeTimer.Time("Empty Method", 10000000, () => { });

结果如下:

而垃圾收集次数的统计,即直观地反应了方法资源分配(消耗)的规模:

int iteration = 100 * 1000;
 
string s = "";
CodeTimer.Time("String Concat", iteration, () => { s += "a"; });
 
StringBuilder sb = new StringBuilder();
CodeTimer.Time("StringBuilder", iteration, () => { sb.Append("a"); });

结果如下:

老赵最近在研究一个问题的几种不同做法在性能上的优劣,其中CodeTimer起到了很重要的作用——这边也先卖个关子,接下来老赵也将会写几篇文章来讲解这个问题。

 

注1:统计CPU时钟周期时使用P/Invoke访问QueryThreadCycleTime函数,这是Vista和Server 2008中新的函数。感谢装配脑袋在这里提供的帮助。

注2:对于.NET 2.0及Vista以下操作系统,请参考《对老赵写的简单性能计数器的修改》

  • Categories: 实践优化,.Net框架
  • Tags: 性能

·        对老赵写的简单性能计数器的修改

·            早上看到老赵写的这个性能计数器,感觉很实用,不过老赵用了很多.C# 3.0 的新语法,还用了 VISTA 和 Server 2008 下特有的Win32 API,对于还在用C#2.0 或者还工作在 XP 或者Server 2003 下的兄弟们,只能望代码心叹了。应老赵的要求,我修改了他的代码,增加了对低版本C# 和低版本windows 操作系统的支持。

·            老赵的原文:一个简单的性能计数器:CodeTimer

·            修改说明

·            1. 采用接口 取代了原代码中的 Lambda 表达式

·            2. 采用 GetThreadTimes 这个API 函数替代了原代码中的 QueryThreadCycleTime

·            这里需要说明的是 GetThreadTimes 给出了线程在内核态和用户态占用的时间,单位是 100ns。两个时间的总和就是线程占用的CPU时间。这个API的时间精度我看了一些资料似乎没有达到 100ns. 所以GetThreadTimes 这个API函数的进度没有 QueryThreadCycleTime 高。

·            下面是我修改后的代码

·            注释1: 2009-03-11 增加委托的调用,修改 GC.Collect 参数,兼容.Net 2.0.  增加每次调用时间统计 

·            增加了委托调用后,我发现同样是测试空函数,采用接口比采用委托效率要略高一些,这和我的预计基本吻合,因为委托不是单纯的函数调用,具体原理超出本文范围,我就不多说了。

·         

·         

·         using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
  
 
    public interface IAction
    {
        void Action();
    }

    public static class CodeTimer
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetThreadTimes(IntPtr hThread, out long lpCreationTime,
           out long lpExitTime, out long lpKernelTime, out long lpUserTime);

        [DllImport("kernel32.dll")]
        static extern IntPtr GetCurrentThread();

        public delegate void ActionDelegate();

        private static long GetCurrentThreadTimes()
        {
            long l;
            long kernelTime, userTimer;
            GetThreadTimes(GetCurrentThread(), out l, out l, out kernelTime, 
               out userTimer);
            return kernelTime + userTimer;
        }

        static CodeTimer()
        {
            Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
            Thread.CurrentThread.Priority = ThreadPriority.Highest;

        }

        public static void Time(string name, int iteration, ActionDelegate action)
        {
            if (String.IsNullOrEmpty(name))
            {
                return;
            }

            if (action == null)
            {
                return;
            }

            //1. Print name
            ConsoleColor currentForeColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine(name);


            // 2. Record the latest GC counts
            //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.Collect(GC.MaxGeneration);
            int[] gcCounts = new int[GC.MaxGeneration + 1];
            for (int i = 0; i <= GC.MaxGeneration; i++)
            {
                gcCounts[i] = GC.CollectionCount(i);
            }

            // 3. Run action
            Stopwatch watch = new Stopwatch();
            watch.Start();
            long ticksFst = GetCurrentThreadTimes(); //100 nanosecond one tick

            for (int i = 0; i < iteration; i++) action();
            long ticks = GetCurrentThreadTimes() - ticksFst;
            watch.Stop();

            // 4. Print CPU
            Console.ForegroundColor = currentForeColor;
            Console.WriteLine("\tTime Elapsed:\t\t" + 
               watch.ElapsedMilliseconds.ToString("N0") + "ms");
            Console.WriteLine("\tTime Elapsed (one time):" + 
               (watch.ElapsedMilliseconds / iteration).ToString("N0") + "ms");

            Console.WriteLine("\tCPU time:\t\t" + (ticks * 100).ToString("N0"
               + "ns");
            Console.WriteLine("\tCPU time (one time):\t" + (ticks * 100 / 
               iteration).ToString("N0") + "ns");

            // 5. Print GC
            for (int i = 0; i <= GC.MaxGeneration; i++)
            {
                int count = GC.CollectionCount(i) - gcCounts[i];
                Console.WriteLine("\tGen " + i + ": \t\t\t" + count);
            }

            Console.WriteLine();

        }

        public static void Time(string name, int iteration, IAction action)
        {
            if (String.IsNullOrEmpty(name))
            {
                return;
            }

            if (action == null)
            {
                return;
            }

            //1. Print name
            ConsoleColor currentForeColor = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine(name);


            // 2. Record the latest GC counts
            //GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
            GC.Collect(GC.MaxGeneration);
            int[] gcCounts = new int[GC.MaxGeneration + 1];
            for (int i = 0; i <= GC.MaxGeneration; i++)
            {
                gcCounts[i] = GC.CollectionCount(i);
            }

            // 3. Run action
            Stopwatch watch = new Stopwatch();
            watch.Start();
            long ticksFst = GetCurrentThreadTimes(); //100 nanosecond one tick
            
            for (int i = 0; i < iteration; i++) action.Action();
            long ticks = GetCurrentThreadTimes() - ticksFst;
            watch.Stop();

            // 4. Print CPU
            Console.ForegroundColor = currentForeColor;
            Console.WriteLine("\tTime Elapsed:\t\t" + 
               watch.ElapsedMilliseconds.ToString("N0") + "ms");
            Console.WriteLine("\tTime Elapsed (one time):" + 
               (watch.ElapsedMilliseconds / iteration).ToString("N0") + "ms");

            Console.WriteLine("\tCPU time:\t\t" + (ticks * 100).ToString("N0"
                + "ns");
            Console.WriteLine("\tCPU time (one time):\t" + (ticks * 100 / 
                iteration).ToString("N0") + "ns");

            // 5. Print GC
            for (int i = 0; i <= GC.MaxGeneration; i++)
            {
                int count = GC.CollectionCount(i) - gcCounts[i];
                Console.WriteLine("\tGen " + i + ": \t\t\t" + count);
            }

            Console.WriteLine();

        }
    }

·           

·        测试类

·         

·         

·            public class TestSleep3000 : IAction
    {
        
#region IAction Members

        
public void Action()
        {
            Thread.Sleep(
3000);
        }

        
#endregion
    }


    
public class TestEmptyMethod : IAction
    {
        
#region IAction Members

        
public void Action()
        {
        }

        
#endregion
    }

    
public class TestStringConcat : IAction
    {
        
string s = "";

        
#region IAction Members

        
public void Action()
        {
            s += 
"a";
        }

        
#endregion
    }

    
public class TestStringBuilderConcat : IAction
    {
        StringBuilder s = 
new StringBuilder();

        
#region IAction Members

        
public void Action()
        {
            s.Append (
"a");
        }

        
#endregion
    }

·         

·        测试代码

·        采用接口 

·                    CodeTimer.Time("Thread Sleep"1new TestSleep3000());
            CodeTimer.Time(
"Thread Sleep"10000000new TestEmptyMethod());
            CodeTimer.Time(
"String Concat"100000new TestStringConcat());
            CodeTimer.Time(
"StringBuilder Conca"100000
                 
new TestStringBuilderConcat()); 

·         

·        测试结果

·         

·         

·        Thread Sleep
        TimeElapsed:           2,997ms
        Time Elapsed (one time):2,997ms
        CPUtime:              0ns
        CPU time (onetime):    0ns
        Gen0:                 0
        Gen 1:                 0
        Gen2:                 0

·        Empty Method
        TimeElapsed:           138ms
        Time Elapsed (one time):0ms
        CPUtime:              125,000,000ns
        CPU time (onetime):    12ns
        Gen0:                 0
        Gen1:                 0
        Gen2:                 0

·        String Concat
        TimeElapsed:           10,547ms
        Time Elapsed (one time):0ms
        CPUtime:              10,546,875,000ns
        CPU time (onetime):    105,468ns
        Gen0:                 4102
        Gen1:                 2661
        Gen2:                 2545

·        StringBuilder Conca
        TimeElapsed:           4ms
        Time Elapsed (one time):0ms
        CPUtime:              0ns
        CPU time (one time):   0ns
        Gen0:                 0
        Gen1:                 0
        Gen2:                 0

·         

·        采用委托

·         

·         CodeTimer.Time("Thread Sleep"1delegate() { Thread.Sleep(3000); });
CodeTimer.Time("Empty Method"10000000delegate() { });

string a = "";

CodeTimer.Time("String Concat"100000delegate() { a += "a"; });

StringBuilder s = new StringBuilder();
CodeTimer.Time("StringBuilder Conca"100000delegate() { s.Append("a"); }); 

·         

·         测试结果

·         

·        Thread Sleep
        TimeElapsed:           2,989ms
        Time Elapsed (one time):2,989ms
        CPUtime:              0ns
        CPU time (onetime):    0ns
        Gen0:                 0
        Gen1:                 0
        Gen2:                 0

·        Empty Method
        TimeElapsed:           156ms
        Time Elapsed (one time):0ms
        CPUtime:              156,250,000ns
        CPU time (onetime):    15ns
        Gen0:                 0
        Gen1:                 0
        Gen2:                 0

·        String Concat
        TimeElapsed:           10,425ms
        Time Elapsed (one time):0ms
        CPUtime:              10,406,250,000ns
        CPU time (onetime):    104,062ns
        Gen0:                 4102
        Gen1:                 2661
        Gen2:                 2545

·        StringBuilder Conca
        TimeElapsed:           4ms
        Time Elapsed (one time):0ms
        CPUtime:              0ns
        CPU time (onetime):    0ns
        Gen0:                 0
        Gen 1:                 0
        Gen2:                 0

体面地处理程序的未捕获异常

经常有客户抱怨程序遇到错误的时候程序就退出了,甚至来不及保存窗体的输入数据,如果是普通的搜索界面也就罢了,但如果客户是呼叫中心,问题就严重了.按道理,我们的程序员应该处理所有的异常,但有事实上做到的很难,因为出乎意料的情况太多了.

在这篇文章,我给大家介绍一下我的处理方法。

首先介绍相应的C#相关知识:

System.Windows.Forms.Application类

提供 static 方法和属性以管理应用程序,例如启动和停止应用程序、处理 Windows 消息的方法和获取应用程序信息的属性。

 

System.Windows.Forms.Application.ThreadException 事件

在发生未捕获线程异常时发生。

 

System.Windows.Forms.Application.SetUnhandledExceptionMode()方法

指示应用程序如何响应未处理的异常。

·    SetUnhandledExceptionMode(UnhandledExceptionMode)

指示应用程序如何响应未处理的异常。

·    SetUnhandledExceptionMode(UnhandledExceptionMode,Boolean)

指示应用程序如何响应未处理的异常,同时可选择应用特定于线程的行为。

 

System.Windows.Forms.UnhandledExceptionMode枚举

定义 Windows 窗体应用程序应在何处发送未处理的异常。

public enumUnhandledExceptionMode

{

Automatic,        //将所有异常都传送到 ThreadException 处理程序,除非应用程序的配置文件指定了其他位置。

ThrowException,    //从不将异常传送到 ThreadException 处理程序。忽略应用程序配置文件。

CatchException        //始终将异常传送到 ThreadException 处理程序。忽略应用程序配置文件。

}

 

以下是我的实现示例:

/// <summary> 
/// 应用程序的主入口点。 
/// </summary> 
[STAThread] 
static void Main() 

    
 
    //应用程序的主入口点添加ThreadException的事件处理。 
    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); 
    
 

 
static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) 

    
//作为示例,这里用消息框显示异常的信息 
    MessageBox.Show(e.Exception.Message,"异常",MessageBoxButtons.OK,MessageBoxIcon.Error); 
}

Tag标签: .NET,C#,Code,CSharp,DotNet,代码

.NET4.0 之 Dynamic VS Reflection 效率

     在我先前的文章中,不断的推广.NET4.0新特性。特别是.NET4.0 Dynamic 这个新特性。随之而来的问题也出现了—Dynamic 执行效率如何?

我们做开发的不光需要代码简洁,能够希望自己能够写出好的架构。还有一点非常重要的就是,我们的写出来的代码效率。撇开Dynamic在.net4.0中的实现原理。本篇只考虑dynamic 的效率比起使用反射到底是快还是慢?难道.NET为引入了dynamic这个新鲜事物降低了我们的程序效率?有网友指出评论一个特性或者方法的效率如何,唯一的判定标准就是实测

     切入主题,按照以下代码,来结束你对dynamic的效率怀疑吧!!!

     1、新建测试类:

view source

print?

1

public class TestClass

2

        {

 

3

            public string TestProperty { get; set; }

4

        }

 

   2、控制台程序进行效率测试代码:

view source

print?

01

static void Main(string[] args)

02

       {

 

03

           int times = 1000000;

04

 

 

05

           string value = "Dynamic VS  Reflection";

06

 

 

07

           //reflection 测试开始

08

           TestClass testTypeByReflection = new TestClass();

 

09

           Stopwatch watch1 = Stopwatch.StartNew();

10

           var property = typeof(TestClass).GetProperty("TestProperty");

 

11

           for (var i = 0; i < times; i++)

12

           {

 

13

               property.SetValue(testTypeByReflection, value, null);

14

           }

 

15

           Console.WriteLine(string.Format("Reflection耗时:{0} 毫秒", watch1.ElapsedMilliseconds));

16

 

 

17

 

18

           //dynamic 测试开始

 

19

           Stopwatch watch2 = Stopwatch.StartNew();

20

           dynamic testTypeByDynamic = new TestClass();

 

21

           for (int i = 0; i < times; i++)

22

           {

 

23

               testTypeByDynamic.TestProperty = value;

24

           }

 

25

           Console.WriteLine(string.Format("Dynamic耗时:{0} 毫秒", watch2.ElapsedMilliseconds));

26

 

 

27

           Console.ReadLine();

28

       }



    3、测试TestClass类,开始 dynamic VS reflection 测试结果如下

     



    怎样,dynamic 比起reflection 够快吧?!

     

最后希望本篇文章可以给您带来帮助,如有不足之处欢迎指出,谢谢!


作者:RyanDing
出处:http://www.cnblogs.com/ryanding/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如有疑问,可以通过 ryan.d@qq.com 联系作者本人。

发表评论

1988263

 回复 引用 查看   

#1楼2010-12-14 13:51 |Jeffrey Zhao      

关键是“为什么”

 回复 引用 查看   

#2楼2010-12-14 13:54 |ejiyuan      

跨类调用时候 就识别不了了

 回复 引用 查看   

#3楼[楼主]2010-12-1413:55 |ryanding      

引用Jeffrey Zhao:关键是“为什么”
内部实现,我稍候再去研究。先把效率提上。排除大家对dynamic效率的担心。

 回复 引用 查看   

#4楼2010-12-14 14:02 |toEverybody      

。NET上有什么效率可谈的呢、?

 回复 引用 查看   

#5楼2010-12-14 14:03 |韦恩卑鄙v-zhewg @waynebaby      

你用的也不是最优的反射呀

 回复 引用 查看   

#6楼2010-12-14 14:03 |Jeffrey Zhao      

@ryanding
严格来说一个实验是不够的,有没有问题要结合实际场景来,因为“担心”而不去用的人,其实是盲目的……所以还是要谈为什么……

 回复 引用 查看   

#7楼[楼主]2010-12-1414:04 |ryanding      

引用toEverybody:。NET上有什么效率可谈的呢、?
你这样说,还真的没法回答了。

 回复 引用 查看   

#8楼[楼主]2010-12-1414:04 |ryanding      

引用韦恩卑鄙 v-zhewg@waynebaby:你用的也不是最优的反射呀

了解:你的意思就是用最优的反射来比较。这样才有说服力!

 回复 引用 查看   

#9楼[楼主]2010-12-1414:05 |ryanding      

引用Jeffrey Zhao:
@ryanding
严格来说一个实验是不够的,有没有问题要结合实际场景来,因为“担心”而不去用的人,其实是盲目的……所以还是要谈为什么……
老赵就是老赵,心悦诚服!

 回复 引用 查看   

#10楼2010-12-14 14:10 |Aloner [Sofire]      

dynamic 第一次用的比较久,其后效率不错。其原理,我个人猜测委托。

效率上:
1、Delegate.CreateDelegate
2、Emit(其最终也是生成委托)

第一个是最恐怖的,那效率啊……竟然丝毫不差……可惜是强类型。所以才有 Emit。

 回复 引用 查看   

#11楼2010-12-14 14:13 |韦恩卑鄙v-zhewg @waynebaby      

把反射做成代理缓存起来 效果吓人一跳。

我记得dynamic 的原理是类似的,
所以我按照原理直接做理当比dynamic封装的逻辑快,

view source

print?

01

   //reflection 测试开始 

02

 

 

03

            TestClass testTypeByReflection = new TestClass();

04

 

 

05

            Stopwatch watch1 = Stopwatch.StartNew();

06

 

 

07

            var property = typeof(TestClass).GetProperty("TestProperty");

08

            var setMethod=property.GetSetMethod();

 

09

            var delg = (Action<TestClass, String>)Delegate.CreateDelegate(typeof(Action<TestClass, String>), setMethod);

10

            for (var i = 0; i < times; i++)

 

11

            {

12

 

 

13

                //property.SetValue(testTypeByReflection, value, null);

14

                delg(testTypeByReflection, value);

 

15

            }

16

 

 

17

            Console.WriteLine(string.Format("Reflection耗时:{0} 毫秒", watch1.ElapsedMilliseconds));

18

 

 

19

//Reflection耗时:62 毫秒

20

//Dynamic耗时:283 毫秒

 

 回复 引用 查看   

#12楼2010-12-14 14:15 |温景良(Jason)      

引用Jeffrey Zhao:关键是“为什么”
老赵解答下

 回复 引用 查看   

#13楼2010-12-14 14:16 |Jeffrey Zhao      

@温景良(Jason)
dynamic根据CallSite缓存Emit出来的结果咯。

 回复 引用 查看   

#14楼2010-12-14 14:20 |韦恩卑鄙v-zhewg @waynebaby      

[s]反编后真相大白 看起来没做delegate缓存嘛?
(不排除CallSite<Func<CallSite, object, string, object>>.Create内部做了缓存,这里面我反编不了了)[/s]

仔细看看还是做了缓存的
if (<Main>o__SiteContainer0.<>p__Site1== null)

view source

print?

01

private static void Main(string[] args)

02

{

 

03

    int times = 0x989680;

04

    string value = "Dynamic VS  Reflection";

 

05

    TestClass testTypeByReflection = new TestClass();

06

    Stopwatch watch1 = Stopwatch.StartNew();

 

07

    MethodInfo setMethod = typeof(TestClass).GetProperty("TestProperty").GetSetMethod();

08

    Action<TestClass, string> delg = (Action<TestClass, string>) Delegate.CreateDelegate(typeof(Action<TestClass, string>), setMethod);

 

09

    for (int i = 0; i < times; i++)

10

    {

 

11

        delg.Invoke(testTypeByReflection, value);

12

    }

 

13

    Console.WriteLine(string.Format("Reflection耗时:{0} 毫秒", watch1.ElapsedMilliseconds));

14

    Stopwatch watch2 = Stopwatch.StartNew();

 

15

    object testTypeByDynamic = new TestClass();

16

    for (int i = 0; i < times; i++)

 

17

    {

18

        if (<Main>o__SiteContainer0.<>p__Site1 == null) //缓存实现

 

19

        {

20

            <Main>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, string, object>>.Create(Binder.SetMember(CSharpBinderFlags.None, "TestProperty", typeof(Program), newCSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) })); //@Jeffz_cn dynamic根据CallSite缓存Emit出来的结果咯。

    

 

21

 

22

        }

 

23

        <Main>o__SiteContainer0.<>p__Site1.Target.Invoke(<Main>o__SiteContainer0.<>p__Site1, testTypeByDynamic, value);

24

    }

    

 

25

    Console.WriteLine(string.Format("Dynamic耗时:{0} 毫秒", watch2.ElapsedMilliseconds));

26

    Console.ReadLine();

 

27

}

 

 回复 引用 查看   

#15楼2010-12-14 14:21 |韦恩卑鄙v-zhewg @waynebaby      

引用Aloner [Sofire]:
dynamic 第一次用的比较久,其后效率不错。其原理,我个人猜测委托。

效率上:
1、Delegate.CreateDelegate
2、Emit(其最终也是生成委托)

第一个是最恐怖的,那效率啊……竟然丝毫不差……可惜是强类型。所以才有 Emit。
强类型也不打紧的你看我上面的例子

 回复 引用 查看   

#16楼2010-12-14 14:24 |沉默杨仔      

@Jeffrey Zhao
老赵也来篇.

 回复 引用 查看   

#17楼2010-12-14 14:28 |辰      

我还以为dynamic是编译器的语法糖,编译的时候自动寻找IL了。

原来还是用了emit这些。

 回复 引用 查看   

#18楼[楼主]2010-12-1414:31 |ryanding      

人多真的力量大。感谢 JeffreyZhao 、韦恩卑鄙 v-zhewg@waynebaby、Aloner [Sofire] 。

 回复 引用 查看   

#19楼2010-12-14 14:34 |韦恩卑鄙v-zhewg @waynebaby      

@辰
emit 和il有啥区别呢不懂il的人问一下

 回复 引用 查看   

#20楼2010-12-14 14:35 |韦恩卑鄙v-zhewg @waynebaby      

@ryanding
vb .net的延迟绑定基本上也是类似的代码形式做的语法糖 人家可是从.net 1.0就支持了。不过它1没用emit 2没用缓存 慢到飞起

 回复 引用 查看   

#21楼[楼主]2010-12-1414:41 |ryanding      

@韦恩卑鄙 v-zhewg@waynebaby
嗯,经过你们的大力支持。我对dynamic的内部实现也有了一个更深入的认识,很不错~

 回复 引用 查看   

#22楼[楼主]2010-12-1414:42 |ryanding      

@韦恩卑鄙 v-zhewg@waynebaby
特别是你提出来的那段把反射做成代理缓存起来的测试代码。非常有力道。呵呵

 回复 引用 查看   

#23楼2010-12-14 14:54 |zhaohua_wang      

引用韦恩卑鄙 v-zhewg@waynebaby:
把反射做成代理缓存起来效果吓人一跳。

我记得dynamic 的原理是类似的,
所以我按照原理直接做理当比dynamic封装的逻辑快,

[code=csharp]
//reflection 测试开始

TestClass testTypeByReflection = new TestClass();

Stopwatch watch1 = Stopwatch.StartNew();

var property = typeof(TestClass).GetPr...

缓存是个好方法!

 回复 引用 查看   

#24楼2010-12-14 14:56 |韦恩卑鄙v-zhewg @waynebaby      

修改了下你的
又做了一个测试如果用dictionary 作为代理的缓存delegate实现的速度反降了



view source

print?

01

static void Main(string[] args)

02

        {

 

03

 

04

            int times = 1000000000;

 

05

 

06

 

 

07

 

08

            string value = "Dynamic VS  Reflection";

 

09

 

10

 

 

11

 

12

            //reflection 测试开始 

 

13

 

14

            TestClass testTypeByReflection = new TestClass();

 

15

 

16

     

 

17

 

18

            var property = typeof(TestClass).GetProperty("TestProperty");

 

19

            var setMethod=property.GetSetMethod();

20

            var delg = (Action<TestClass, String>)Delegate.CreateDelegate(typeof(Action<TestClass, String>), setMethod);

 

21

            var dic = new Dictionary<string, Action<TestClass, String>>()  //做缓存总得有个字典吧

22

            {

 

23

                  {  "aaaaaa",delg}

24

            };

 

25

            Stopwatch watch1 = Stopwatch.StartNew();

26

            for (var i = 0; i < times; i++)

 

27

            {

28

                if (dic.TryGetValue("aaaaaa", out delg))  //每次从字典中取得delegate

 

29

                {

30

                //property.SetValue(testTypeByReflection, value, null);

 

31

                    delg(testTypeByReflection, value);

32

                }

 

33

            }

34

 

 

35

            Console.WriteLine(string.Format("Reflection耗时:{0} 毫秒", watch1.ElapsedMilliseconds));

36

 

 

37

 

38

 

 

39

 

40

 

 

41

            //dynamic 测试开始 

42

            dynamic testTypeByDynamic = new TestClass();

 

43

            Stopwatch watch2 = Stopwatch.StartNew();

44

            for (int i = 0; i < times; i++)

 

45

            {

46

 

 

47

                testTypeByDynamic.TestProperty = value;

48

 

 

49

            }

50

 

 

51

            Console.WriteLine(string.Format("Dynamic耗时:{0} 毫秒", watch2.ElapsedMilliseconds));

52

 

 

53

 

54

 

 

55

            Console.ReadLine();

56

 

 

57

        }

58

//Reflection耗时:63037 毫秒

 

59

//Dynamic耗时:26759 毫秒




所以在没有超越dictionary的代理缓存实现前 一般访问器还是dynamic快,因为人家的callsite用的是指针引用,不用算hash什么的。

如果是在某个地方对一两个字段做最快的操作,可以考虑就地建立delegate缓存,用引用保存起来。


 回复 引用 查看   

#25楼2010-12-14 14:59 |韦恩卑鄙v-zhewg @waynebaby      

引用ryanding:
@韦恩卑鄙 v-zhewg@waynebaby
嗯,经过你们的大力支持。我dynamic的内部实现也有了一个更深入的认识,很不错~
你要是不提出题目和测试代码,我也没机会研究研究啊。
某种意义上你是博导我是研究员

希望我的测试能对你有所启发,最好能验证下我的测试,把咱们的实践结果总结到你的主文中去,这样方便了伸手党,咱也没白忙活

 回复 引用 查看   

#26楼[楼主]2010-12-1415:02 |ryanding      

引用韦恩卑鄙 v-zhewg @waynebaby:
引用ryanding:
@韦恩卑鄙 v-zhewg@waynebaby
嗯,经过你们的大力支持。我dynamic的内部实现也有了一个更深入的认识,很不错~
你要是不提出题目和测试代码,我也没机会研究研究啊。
某种意义上你是博导我是研究员

希望我的测试能对你有所启发,最好能验证下我的测试,把咱们的实践结果总结到你的主文中去,这样方便了伸手党,咱也没白忙活
非常好的想法!一会我整理下。把这篇文章改造的详细点。将你的测试结果都放进去。有才的提议!

 回复 引用 查看   

#27楼2010-12-14 15:05 |韦恩卑鄙v-zhewg @waynebaby      

@ryanding
小提醒 stopwatch 要在循环体前面开始 初始化工作不应该纳入计时呵呵

 回复 引用 查看   

#28楼[楼主]2010-12-1415:07 |ryanding      

引用韦恩卑鄙 v-zhewg@waynebaby:
@ryanding
小提醒 stopwatch 要在循环体前面开始 初始化工作不应该纳入计时呵呵

明白,重新改造这篇文章时,我会注意的。

 回复 引用 查看   

#29楼2010-12-14 15:32 |SeaSunK      

@韦恩卑鄙 v-zhewg@waynebaby
哇塞。。反過來整整快了一個數量級

 回复 引用 查看   

#30楼[楼主]2010-12-1415:34 |ryanding      

@SeaSunK
所以不存在快与慢,主要看到底是如何去使用它!

 回复 引用 查看   

#31楼2010-12-14 15:50 |CallHot      

呵呵, 不错的好文,提出了4.0中新引入的动态类型带来的惊喜,所以经过本文证明,新东西好不好用,只有自己试过了才知道!支持!

 回复 引用 查看   

#32楼2010-12-14 16:01 |allentranks      

Good, 好贴~
缓存的力量不可小觑啊~

 回复 引用 查看   

#33楼[楼主]2010-12-1416:03 |ryanding      

引用CallHot:呵呵,不错的好文,提出了4.0中新引入的动态类型带来的惊喜,所以经过本文证明,新东西好不好用,只有自己试过了才知道!支持!
嗯,经过高人们的激烈的讨论后,我总结出:技术只有当我们去深入研究了,才知道它到底好不好用!

 回复 引用 查看   

#34楼[楼主]2010-12-1416:03 |ryanding      

引用allentranks:
Good, 好贴~
缓存的力量不可小觑啊~
同感!

 回复 引用 查看   

#35楼2010-12-14 16:29 |愚溪      

动态类型在解析属性的时候先查引用韦恩卑鄙 v-zhewg@waynebaby:
修改了下你的
又做了一个测试如果用dictionary 作为代理的缓存delegate实现的速度反降了




[code=csharp]
static void Main(string[] args)
{

int times = 1000000000;



string value = "Dynamic VS Reflection";



//reflection 测试开始

TestClass testType...
我想系统不用Dictionary吧。当编译器看到dynamic关键字的时候就会建立一个这样的变量,就跟你反编译出来的Code的表现一样。哪里还需要使用Dictionary缓存啊。

 回复 引用 查看   

#36楼2010-12-14 16:31 |愚溪      

view source

print?

01

public static CallSite Create(Type delegateType, CallSiteBinder binder)

02

    {

 

03

        Func<CallSiteBinder, CallSite> func;

04

        ContractUtils.RequiresNotNull(delegateType, "delegateType");

 

05

        ContractUtils.RequiresNotNull(binder, "binder");

06

        if (!delegateType.IsSubclassOf(typeof(Delegate)))

 

07

        {

08

            throw Error.TypeMustBeDerivedFromSystemDelegate();

 

09

        }

10

        if (_SiteCtors == null)

 

11

        {

12

            _SiteCtors = new CacheDict<Type, Func<CallSiteBinder, CallSite>>(100);

 

13

        }

14

        MethodInfo method = null;

 

15

        CacheDict<Type, Func<CallSiteBinder, CallSite>> dict = _SiteCtors;

16

        lock (dict)

 

17

        {

18

            if (!dict.TryGetValue(delegateType, out func))

 

19

            {

20

//创建了一个delegate

 

21

                method = typeof(CallSite<>).MakeGenericType(new Type[] { delegateType }).GetMethod("Create");

22

                if (delegateType.CanCache())

 

23

                {

24

                    func = (Func<CallSiteBinder, CallSite>) Delegate.CreateDelegate(typeof(Func<CallSiteBinder, CallSite>), method);

 

25

                    dict.Add(delegateType, func);

26

                }

 

27

            }

28

        }

 

29

        if (func != null)

30

        {

 

31

            return func(binder);

32

        }

 

33

        return (CallSite) method.Invoke(null, new object[] { binder });

34

    }



从这里我们可以看出其实Dynamic也是建立了一个Delegate

 回复 引用 查看   

#37楼2010-12-14 16:34 |Ivony...      

dynamic的确就是语法糖,所以我说dynamic是不是一个类型都是值得推敲的,至少dynamic不是一个runtime认识的类型,dynamic关键字就是通知C#编译器将其一切访问都变成CallSite的动态绑定。

 回复 引用 查看   

#38楼2010-12-14 16:42 |gjcn      

这个测试场景不怎么好。

 回复 引用 查看   

#39楼2010-12-14 16:44 |Lenic      

测试的时候最好附上直接调用的开销

 回复 引用 查看   

#40楼2010-12-14 16:45 |Ivony...      

引用愚溪:
[code=csharp]
public static CallSite Create(Type delegateType,CallSiteBinder binder)
{
Func<CallSiteBinder, CallSite> func;
ContractUtils.RequiresNotNull(delegateType,"delegateType");
ContractUtils.RequiresNotNull(binder, "binder");
if (!delegateType.IsSubcl...


这里应该是绑定的缓存,还不是调用的缓存。。。。

 回复 引用 查看   

#41楼2010-12-14 16:52 |愚溪      

sorry看错了我看的是CallSite。 是lambda表达使用的。Dynamic使用的是CallSite<>

其中的Cache使用的是Dictionary<Type,object>

 回复 引用 查看   

#42楼2010-12-14 17:01 |韦恩卑鄙v-zhewg @waynebaby      

@愚溪
這個緩存初始化後就不調用了,你看我上面反編結果中的注釋。

初始化以後都是調用callsite。target。invoke的開銷。

 回复 引用 查看   

#43楼2010-12-14 17:06 |Code Man      

很好的文章,极好的评论,受教了。

 回复 引用 查看   

#44楼2010-12-14 17:14 |Kevin.Jee      

Dynamic类型用着很方面,关键在于它内部会有一个缓存机制,这样的话,第二次使用同一个类型的Dynamic对象就不会有开销。

而反射,相对来说,是一个古老的技术了,其实我并不赞同这样手段的比较,本身反射是不自身缓存的,也就是成本很大,建议笔者如果作比较的话,也应该自己来个反射+缓存,然后再去跟Dynamic去比较,这样兴趣会公平很多。

 回复 引用 查看   

#45楼2010-12-14 17:49 |John Ho      

引用toEverybody:。NET上有什么效率可谈的呢、?
非常好奇,为什么就没有效率可谈

 回复 引用 查看   

#46楼2010-12-14 17:54 |愚溪      

一般常用的反射的地方是什么?
两种Case
一种是访问非public的Member
一种是访问不能直接访问的对象的Member。
很明显Dynamic的目的是代替后者的作用。

 回复 引用 查看   

#47楼2010-12-14 18:01 |bengxia      

dynamic不能算语法糖,是以.NET 4.0引入的DLR( Dynamic Language Runtime :动态语言运行时)为支撑的。
DLR的一个主要特点是规则缓存,在第一次调用时跟反射差不多,但是其后的调用性能逼近静态类型。
参考:http://dotnet.cnblogs.com/page/71440/

 回复 引用 查看   

#48楼2010-12-14 18:39 |周行天下      

@toEverybody
傻逼

 回复 引用 查看   

#49楼2010-12-14 19:22 |金旭亮      

前段时间研究过一阵子dyanmic。
要想弄明白dyanmic,一定得去看DLR。
在楼主的实验中,Dynamic的效率高是因为DLR在内部提供了3级的“Rule”缓存,你如果把反射的结果缓存一下,它不会比dynamic效率低的。
另外,对于C#这种静态语言创建的对象,DLR在内部也应用了反射。但对于其他动态语言(比如IronPython),DLR使用特定编程语言所提供的动态站点绑定对象创建DLR表达式树,然后再调用DLR表达式树的Compile方法直接生成IL指令。
我个人觉得如果把dyanmic也称为“语法糖”,似乎不太合适。.NET 4.0中的CLR版本是4,而.NET 3.5,仍是CLR 2。版本号的升级,应该意味着CLR内部有了比较大的改变,我个人猜测可能CLR针对集成动态语言进行了一些调整和改进。


 回复 引用 查看   

#50楼2010-12-14 20:07 |Jeffrey Zhao      

@金旭亮
CLR 4的变化应该和DLR没有什么关系(至少从没听人提起过,不像JVM上新增的invokedynamic指令被人说了又说),DLR也可以跑在CLR 2上面。C#的dynamic也是基于DLR的,和IronPython之类的其实区别不大,只是在DLR上面使用了不同的Binding罢了,Runtime的缓存等等都是交给DLR做的。

 回复 引用 查看   

#51楼2010-12-14 20:09 |Jeffrey Zhao      

引用Ivony...:dynamic的确就是语法糖,所以我说dynamic是不是一个类型都是值得推敲的,至少dynamic不是一个runtime认识的类型,dynamic关键字就是通知C#编译器将其一切访问都变成CallSite的动态绑定。
简单来看,其实编译器就是把a.Call(x, y, z)变成了Dynamic.Invoke(a,"Call", new[]{x, y, z})之类的。当然,我这里是随手写的,事实上肯定不会是这样的……

 回复 引用 查看   

#52楼2010-12-14 21:57 |辰      

@韦恩卑鄙 v-zhewg@waynebaby
是在损我还是真的在问啊。。。

我说的IL是静态的,以为dynamic在编译器级别就直接被翻译了对应的IL了。emit是程序运行时动态的。

 回复 引用 查看   

#53楼2010-12-14 23:48 |yyww      

引用周行天下:
@toEverybody
傻逼

别骂,别理他就行了,越理越来劲。

 回复 引用 查看   

#54楼2010-12-15 08:11 |yongfa365      

弱弱的问下:
public List<dynamic> DynamicSearchRoom()
{
List<dynamic> list = newList<dynamic>() {
new {RoomID="123", RoomName="豪华单间1", PublicPrice="1800", Price="180"},
new {RoomID="124", RoomName="豪华单间2", PublicPrice="1600", Price="180"},
new {RoomID="125", RoomName="豪华单间3", PublicPrice="1500", Price="180"},
new {RoomID="126", RoomName="豪华单间4", PublicPrice="1400", Price="180"},
new {RoomID="127", RoomName="豪华单间5", PublicPrice="1300", Price="180"}
};
return list;
}
为什么在wcf+jquery里运行不了。

 回复 引用 查看   

#55楼2010-12-15 08:19 |Aloner [Sofire]      

友情提醒:普通测试中,使用dict.TryGetValue或等的集合操作,是影响结果的重要因素之一。所以,封装的时候要谨慎了。

 回复 引用 查看   

#56楼2010-12-15 08:22 |邀月      

该测试不够严谨。
看看我以前做的简陋测试。
http://www.cnblogs.com/downmoon/archive/2008/09/01/1281118.html

 回复 引用 查看   

#57楼[楼主]2010-12-1508:32 |ryanding      

引用yongfa365:
弱弱的问下:
public List<dynamic> DynamicSearchRoom()
{
List<dynamic> list = newList<dynamic>() {
new {RoomID="123", RoomName="豪华单间1", PublicPrice="1800", Price="180"},
new {RoomID="124", RoomName="豪华单间2", PublicPrice="1600", Price="180"},...
将 list 序列化成json.我想你的问题就能解决了吧?

 回复 引用 查看   

#58楼[楼主]2010-12-1508:33 |ryanding      

引用Aloner [Sofire]:友情提醒:普通测试中,使用dict.TryGetValue或等的集合操作,是影响结果的重要因素之一。所以,封装的时候要谨慎了。
原因是什么?这个口吻是学老赵的。

 回复 引用 查看   

#59楼[楼主]2010-12-1508:36 |ryanding      

引用邀月:
该测试不够严谨。
看看我以前做的简陋测试。
http://www.cnblogs.com/downmoon/archive/2008/09/01/1281118.html
比我的这个测试,你的测试专业很多!

 回复 引用 查看   

#60楼2010-12-15 08:38 |Aloner [Sofire]      

@ryanding
~o(╯□╰)o~
无法和老赵相比的~~
ORM中,一般是要对属性进行取值赋值。
这个 getter 或 setter 通常又是极为简单的

public string Username {get;set;}

那么,如果将委托进行缓存的话,其实性能也就大大降低了。
在要求性能的情况下,最好(也是最麻烦)的方法是进行 switch,然后根据 类型进行创建 Delegate.CreateDelegate 委托。
我做个测试,这样的性能几乎毫发无损。

 回复 引用 查看   

#61楼[楼主]2010-12-1508:52 |ryanding      

@Aloner [Sofire]

view source

print?

01

var delg = (Action<TestClass, String>)Delegate.CreateDelegate(typeof(Action<TestClass, String>), setMethod);

02

var dic = new Dictionary<string, Action<TestClass, String>>()  //做缓存总得有个字典吧

 

03

{

04

      {  "aaaaaa",delg}

 

05

};

06

Stopwatch watch1 = Stopwatch.StartNew();

 

07

for (var i = 0; i < times; i++)

08

{

 

09

    if (dic.TryGetValue("aaaaaa", out delg))  //每次从字典中取得delegate

10

    {

 

11

    //property.SetValue(testTypeByReflection, value, null);

12

        delg(testTypeByReflection, value);

 

13

    }

14

}


在下才疏学浅,还是不太明白为何加入dictionary后速度反降了?dictionary内部在算hash么?赐教一下吧?谢谢

 回复 引用 查看   

#62楼2010-12-15 08:56 |yongfa365      

@ryanding
无法序列化类型“<>f__AnonymousType0`4[System.String,System.String,System.String,System.String]”。请考虑将其标以 DataContractAttribute 特性,并将其所有要序列化的成员标以 DataMemberAttribute 特性。如果类型为集合,则请考虑将其标以 CollectionDataContractAttribute 特性。有关其他受支持的类型,请参见 Microsoft .NET Framework 文档。

 回复 引用 查看   

#63楼2010-12-15 08:58 |yongfa365      

对我来说,这样的东西很有用
public List<dynamic> DynamicSearchRoom()
{
List<dynamic> list = newList<dynamic>() {
new {RoomID="123", RoomName="豪华单间1", PublicPrice="1800", Price="180"},
new {RoomID="124", RoomName="豪华单间2", PublicPrice="1600", Price="180"},
new {RoomID="125", RoomName="豪华单间3", PublicPrice="1500", Price="180"},
new {RoomID="126", RoomName="豪华单间4", PublicPrice="1400", Price="180"},
new {RoomID="127", RoomName="豪华单间5", PublicPrice="1300", Price="180"}
};
return list;
}

因为可能返回不同数量的值,如果每个都要写一个实体,比较麻烦

 回复 引用 查看   

#64楼[楼主]2010-12-1509:00 |ryanding      

@yongfa365
不用啊,我在想你为什么不能序列化呢,看我的代码:

view source

print?

1

List<dynamic> list = new List<dynamic>() {

2

                    new {RoomID="123", RoomName="豪华单间1", PublicPrice="1800", Price="180"},

 

3

                    new {RoomID="124", RoomName="豪华单间2", PublicPrice="1600", Price="180"},

4

                    new {RoomID="125", RoomName="豪华单间3", PublicPrice="1500", Price="180"},

 

5

                    new {RoomID="126", RoomName="豪华单间4", PublicPrice="1400", Price="180"},

6

                    new {RoomID="127", RoomName="豪华单间5", PublicPrice="1300", Price="180"}

 

7

                    };

8

            return Json(new { Success = true, Msg = list }, JsonRequestBehavior.AllowGet);  //Json序列化。

 

view source

print?

01

$(function () {

02

            $("#btnTest").click(function () {

 

03

                $.ajax({

04

                    type: "POST",

 

05

                    url: '<%=Url.Action("SaveDepartment") %>',

06

                    dataType: 'json',

 

07

                    cache: false,

08

                    success: function (result) {

 

09

                        if (result.Success) {

10

                            //alert(result.Msg[0].EmployeeName);

 

11

                        }

12

                        alert(result.Msg[0].RoomID);

 

13

                    }

14

                });

 

15

            });


mvc2.0中完全可以序列化啊!

 回复 引用 查看   

#65楼2010-12-15 09:14 |yongfa365      

你的Json发我看下,我使用的是

DataContractJsonSerializer serializer = newDataContractJsonSerializer(list.GetType());
using (MemoryStream ms = new MemoryStream())
{
serializer.WriteObject(ms, list);
return Encoding.UTF8.GetString(ms.ToArray());
}

 回复 引用 查看   

#66楼2010-12-15 09:22 |韦恩卑鄙v-zhewg @waynebaby      

@辰
我是真问啊。我的概念中只觉得emit是在生成il,把运行时动态的特点忘了呵呵

我觉得就算是静态生成il也只能生成按照字符串反射的il,因为很可能传入的类型不同,所以和emit没啥区别呀。


 回复 引用 查看   

#67楼2010-12-15 09:28 |韦恩卑鄙v-zhewg @waynebaby      

@yongfa365
你要用wcf数据契约序列化的话匿名类型肯定有困难,那个很严格的,复杂类型需要datacontract特性支持,换字典吧dictionary<string,object>

view source

print?

01

public List<Dictionary<string,Object>>  DynamicSearchRoom()

02

{

 

03

List<Dictionary<string,Object>  > list = new List<Dictionary<string,Object>  >() {

04

new Dictionary<string,Object>() {

 

05

{"RoomID","123"},

06

{"RoomName","豪华单间1"},

 

07

.....

08

},

 

09

....

10

 

 

11

};

12

return list;

 

13

}

 

 回复 引用 查看   

#68楼[楼主]2010-12-1509:39 |ryanding      

@yongfa365
我在mvc2.0下测试序列化dynamic对象为JSON通过了,是因为我用了System.Web.Mvc 程序集底下的:

view source

print?

01

//

02

    // Summary:

 

03

    //     Creates a System.Web.Mvc.JsonResult object that serializes the specified

04

    //     object to JavaScript Object Notation (JSON) format using the specified JSON

 

05

    //     request behavior.

06

    //

 

07

    // Parameters:

08

    //   data:

 

09

    //     The JavaScript object graph to serialize.

10

    //

 

11

    //   behavior:

12

    //     The content type (MIME type).

 

13

    //

14

    // Returns:

 

15

    //     The result object that serializes the specified object to JSON format.

16

    protected internal JsonResult Json(objectdata, JsonRequestBehavior behavior);

 

 回复 引用 查看   

#69楼2010-12-15 09:41 |韦恩卑鄙v-zhewg @waynebaby      

@ryanding
ajax脚本神马的 和wcf的严格程度是不同的世界。你总不能让人家服务端全用mvc吧 呵呵

 回复 引用 查看   

#70楼[楼主]2010-12-1509:42 |ryanding      

@韦恩卑鄙 v-zhewg@waynebaby
是啊,我意识到了。还有我对WCF一点都不熟悉。。

 回复 引用 查看   

#71楼[楼主]2010-12-1509:48 |ryanding      

@yongfa365
JavaScriptSerializer 替代 DataContractJsonSerializer 行么?

 回复 引用 查看   

#72楼[楼主]2010-12-1509:56 |ryanding      

@韦恩卑鄙 v-zhewg@waynebaby
对了,昨天你在反射测试的时候加了dictionary缓存字段,反而速度降了,内部是由于算hash导致?这点我还是有点迷糊,指教一下啊!

 回复 引用 查看   

#73楼2010-12-15 10:35 |henry      

其实关键还是如何用,就算是反射的效率我们也没有必要太大的怀疑而不去用他.很多人都喜欢举这种例子来说明问题,但实际情况又怎样呢?
就以1000000这个数举例,假设我们每个请求用到了100个反射操作,10000个请求才多耗时3秒,就是说每个多用了0.0003秒(当然实际情况有可能一个多点一个少噗)。这个耗时不要说是数据库操作了,就算一个的函数代码编写方式变化所产生的损耗都有可能比这要多。
系统效率讲求的是整体,而不是某行代码。如同:代码多效率就低?

 回复 引用 查看   

#74楼2010-12-15 11:05 |Aloner [Sofire]      

@ryanding

改成这个(看注释):

Stopwatch watch1 = Stopwatch.StartNew();
for (var i = 0; i < times; i++)
{
if (dic.TryGetValue("aaaaaa", out delg))
{
delg(testTypeByReflection, value);//<-注释它
}
}
然后你运行,看看结果。
dic.TryGetValue效率耗的很恐怖(额……实际也不大啦,只不过在“苛刻”的情况),如果使用dic["aaaaaa"],那更是慢了
所以这是一个悲剧的地方。明明单独反射后创建委托,效率差距一丝,加个缓存反而更慢了。

 回复 引用 查看   

#75楼2010-12-15 11:08 |Aloner [Sofire]      

@henry
其实这有点像是在探讨研究项目的需求。实际开发后,肯定会大不同的。
但是如果有探讨过,我相信会更好一些。
也就是说,先想,再做。
这句话说的很漂亮:其实关键还是如何用,就算是反射的效率我们也没有必要太大的怀疑而不去用他.

 回复 引用 查看   

#76楼2010-12-15 11:11 |铁目诱惑      

不反对所谓的新技术,但完全不支持项目中到处泛滥了这种东西。

 回复 引用 查看   

#77楼[楼主]2010-12-1511:43 |ryanding      

引用Aloner [Sofire]:
@ryanding

改成这个(看注释):

Stopwatch watch1 = Stopwatch.StartNew();
for (var i = 0; i < times; i++)
{
if (dic.TryGetValue("aaaaaa", out delg))
{
delg(testTypeByReflection, value);//<-注释它
}
}
然后你运行,看看结果。
dic.TryGetValue效率耗的很恐怖(额……实际也不大啦,只不过在“苛刻”的情况),如果使用dic["aaaaaa"],那更是慢了
所以这是一个悲剧的地方。明明单独反射后创建委托,效率差距一丝,加个缓存反而更慢了。
好的,多谢!我运行试下效果。

 回复 引用 查看   

#78楼[楼主]2010-12-1511:44 |ryanding      

引用铁目诱惑:不反对所谓的新技术,但完全不支持项目中到处泛滥了这种东西。
没说到处泛滥的使用,要用在该用的地方!

 回复 引用 查看   

#79楼2010-12-1513:33 |韦恩卑鄙 v-zhewg @waynebaby      

@ryanding
上次只是提供了一个hashtable 的Dictionary
有更快的方法的话可以考虑建立自己的IDicationary,比如已经实现了的SortedDictionary就是基于红黑树的。

动手实现扩展属性为对象动态添加获取数据

     由于项目需要常常会遇到为某一个对象动态添加属性的情况,而以前我的实现方式是创建一个字典用于存放对象实例和它的值,但是往往光这么做是不够的,例如想在对象的某个属性值改变的时候做点什么都要写很多的代码,所以想是不是能够将这一类功能进行一下封装。后来因为学习WPF的缘故,想到依赖属性的思想和我需要的功能相近,但是又不能叫我把每一个想要添加扩展的对象类都去继承DependencyObject吧,而且有些类是封闭的不能够继承,所以依赖属性不能满足我的需求。不过说到底依赖属性还是个不错的东西,接下来我们将实现一个类似的东西 - 扩展属性。

   在实现扩展属性时我也参考了依赖属性的源码,它的设计思想的确很“先进”。

   1.先来看看扩展属性的使用方式:

   1:privatestatic ExtendProperty InfoProperty = 
   2:      ExtendProperty.RegisterProperty("Info", typeof(string), typeof(UserInfo),"you win");
   3: var user = new UserInfo() { Age=21, Name="maxzhang" };
   4:  
   5: user.SetValue(InfoProperty, "hello");
   6:string rrr = (string)user.GetValue(InfoProperty);

是不是看着特别像依赖属性呢,往下面看:

   1: dynamic userDynamic = user.AsDynamic();
   2: rrr= userDynamic.Info;
   3: userDynamic.Info = "1";
   4: userDynamic.Age = 50;
   5: rrr = userDynamic.Info;

我为扩展属性添加了动态性使对象属性的创建和访问更加方便,这里如果Info属性在前面没有用RegisterProperty方法定义过它会自动生成一个扩展属性且添加属性值.如果访问了它的普通属性属性也是正常使用的。以上两个例子中

UserInfo类的定义是 public classUserInfo : ExtendObject { public string Name { set; get; } public int Age {set; get; }},你可能会问这不是和依赖属性一样吗?只是把继承DependencyObject换成了继承你自己写的ExtendObject了。是的这样看是差不多的,不过以上的情况还是有一个好处的就是我可以在任何项目里引用它

如果遇到了不能继承的情况呢,其实这种情况有很多。接… 

public class UserInfo1{ public string Name{set;get;} }    这个类不继承任何类。

解决它这里引入了新的扩展类型AttachObject

   1: AttachObject user1Aobj = new AttachObject(user1);
   2: var dyuser = user1Aobj.ToDynamicAttachObject();
   3://var dyuser = user1.ToDynamicAttachObject();
   4: dyuser.Memo = "haha my name i's maxzhang......";
   5: rrr = dyuser.Memo;

其实AttachObject 类型也是一个ExtendObject可以把它看成是一个ExtendObject的装饰。

        2.下面我们来看看这些都是怎么实现的

        (1).ExtendProperty

        与依赖属性类似,在ExtendProperty类中用了一个Dictionary<int,ExtendProperty>来存储系统中要用到的扩展属性,这样实现也达到了节省内存资源的目地。且这个类的构造器是一个private的,这样也就实现了一个单例模式,只有在RegisterProperty方法才能创造出一个ExtendProperty来.

  RegisterPropertypublic static ExtendProperty RegisterProperty(string propertyName, Type   propertyType, Type ownerType,object defaultValue)
{
      var property = new ExtendProperty(propertyName, propertyType,ownerType);
      property.OverrideDefaultValue(ownerType, defaultValue);
            ExtendPropertysProvider.Set(property.GetHashCode(), property);
 
      return property;
}

用GetHashCode来标示我们这个属性的唯一性,这里我重写了这个函数它的值是 this.ownerType.GetHashCode()^this.propertyName.GetHashCode(),也就是说用注册这个属性的类型和属性的名称确定了这个扩展属性。我们看到OverrideDefaultValue这个方法它是用来重写属性的默认值的,在这个系统中如果某个对象的扩展属性没有赋过值或说没有改变过,那么它应该在访问这个属性的时候取得一个默认值而且这 个默认值应该是所有相同注册类型的对象共有的,而在用普通属性存储的对象中我们实例化对象后会在每一个对象中保存相应的默认值,这样无疑是浪费了内存。而且OverrideDefaultValueAddOwner方法一起使用可以达到属性继承的目的。我们来看看AddOwner方法的实现:

  AddOwnerpublic ExtendProperty AddOwner(Type ownerType,object defaultValue)
{
    int newOwnerHash = ownerType.GetHashCode() ^ this.PropertyName.GetHashCode();
    if(defaultValue!=null)
       this.OverrideDefaultValue(ownerType, defaultValue);
    ExtendPropertysProvider.Set(newOwnerHash, this);
    return this;
}

使用AddOwner方法我们就在原有的扩展属性上添加了一个指向它的引用从而达到继承的目地,怎么重写属性默认值呢?其实很简单默认值在扩展属性中保存在一个<type,object>的字典中通过不同的类型我们就可以访问不同类型的相同属性的默认值了。

      (2).ExtendObject

       这里ExtendObject就没什么好说的了,原理就是其内部有一个Dictionary<int,object> propertyValues 存储着不同对象的值,用自身的GetHashCode ^ 扩展属性的HashCode 确定值的唯一性。

  ExtendObject的源码,呵呵 public class ExtendObject
    {
        protected Dictionary<int, object> propertyValues = new Dictionary<int, object>();
        private Type OwnerType = null;
 
        public ExtendObject()
        {
            OwnerType = this.GetType();
        }
 
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
 
        public virtual object GetOwner()
        {
            return this;
        }
 
        protected void AttachOwner(Type ownerType)
        {
            this.OwnerType = ownerType;
        }
 
        public bool IsExtendProperty(string propertyName)
        {
            return !OwnerType.GetProperties().Any(p => p.Name == propertyName); ;
        }
 
        protected ExtendProperty GetProperty(string name)
        {
            int propertyKey = OwnerType.GetHashCode() ^ name.GetHashCode();
            var property = ExtendPropertysProvider.Get(propertyKey);
            return property;
        }
 
        public object GetValue(ExtendProperty property)
        {
 
            int propertyHash = property.GetHashCode();
            int key = this.GetHashCode() ^ propertyHash;
 
            object result = null;
            if (!propertyValues.TryGetValue(key, out result))
            {
                result = property.GetDefaultValue(this.OwnerType);
            }
            return result;
        }
 
        public bool ClearValue(ExtendProperty property)
        {
            bool result = false;
            int propertyHash = property.GetHashCode();
            int key = this.GetHashCode() ^ propertyHash;
 
            if (propertyValues.Keys.Any(k => k == key))
            {
                propertyValues.Remove(key);
                result = true;
            }
            return result;
        }
 
        public void SetValue(ExtendProperty property, object value)
        {
            var changedItemArgs = new ExtendPropertyValueChangedArgs();
            int propertyHash = property.GetHashCode();
            int key = this.GetHashCode() ^ propertyHash;
 
            if (propertyValues.Keys.Any(k => k == key))
            {
                changedItemArgs.OldValue = propertyValues[key];
                propertyValues[key] = value;
            }
            else
            {
                changedItemArgs.OldValue = null;
                propertyValues.Add(key, value);
            }
 
            changedItemArgs.Item = GetOwner();
            changedItemArgs.PropertyType = property.PropertyType;
            changedItemArgs.PropertyName = property.PropertyName;
            changedItemArgs.NewValue = value;
 
            property.OnValueChanged(changedItemArgs);
        }
 
        public bool ClearValue(string propertyName)
        {
            var property = this.GetProperty(propertyName);
            if (property != null)
                return this.ClearValue(property);
 
            return false;
        }
 
        public object GetValue(string propertyName)
        {
            var property = this.GetProperty(propertyName);
            if (property != null)
                return this.GetValue(property);
 
            return null;
        }
 
        public void SetValue(string propertyName, object value)
        {
            var property = this.GetProperty(propertyName);
 
            if (property != null)
            {
                this.SetValue(property, value);
            }
            else
            {
                var newProperty = ExtendProperty.RegisterProperty(propertyName, typeof(object), OwnerType);
                this.SetValue(newProperty, value);
            }
        }
 
        public ExtendDynamicObject AsDynamic()
        {
            return new ExtendDynamicObject(this);
        }
 
    }

不过这里还是有一个小小的技巧的就是OwnerType这个属性和AttachOwner方法,默认的OwnerType属性的值是扩展对象本身的Type,但是通过AttachOwner方法我们可以改变这个属性从而达到将不继承自ExtendObject类型的对象装饰成ExtendObject

对象的目地。

      (3).也就是AttachObject

       AttachObject类通过调用AttachOwner方法使用了这个技巧,同时把同样为ExtendObject的对象的属性统统都Copy过来.

  AttachObjectpublic class AttachObject : ExtendObject
    {
        private object owner;
 
        public AttachObject(object obj)
            : base()
        {
            owner = obj;
            if (owner is ExtendObject)
            {
                Type ownerType = typeof(ExtendObject);
                FieldInfo fInfo = ownerType.GetField("propertyValues", BindingFlags.Default | BindingFlags.NonPublic | BindingFlags.Instance);
                var ownerValues = fInfo.GetValue(owner) as Dictionary<int, object>;
 
                foreach (var v in ownerValues)
                    this.propertyValues.Add(v.Key, v.Value);
 
            }
            this.AttachOwner(owner.GetType());
        }
 
        public override object GetOwner()
        {
            return owner;
        }
 
        public override int GetHashCode()
        {
            return owner.GetHashCode();
        }
    }

 

 

今天到这里

下一节中我将介绍如何实现动态性以及一些使用场景,代码下载……

 


原创粉丝点击