views:

437

answers:

4
private Equipment GenerateDirtyPropertiesOnEntity(Equipment updatedEntity)
    {
        updatedEntity.DirtyProperties.Clear();
        Equipment originalEntity = GetEquipmentByGuid(updatedEnitity.Guid.Value);
        Type myType = updatedEntity.GetType();
        System.Reflection.PropertyInfo[] properties = myType.GetProperties();
        foreach (System.Reflection.PropertyInfo p in properties)
        {
            if (p.GetValue(originalEntity, null) == null)
            {
                if (p.GetValue(updatedEntity, null) != null)
                    updatedEntity.DirtyProperties.Add(p.Name);
            }
            else
            {
                if (!(p.GetValue(originalEntity, null).Equals(p.GetValue(updatedEntity, null))))
                    updatedEntity.DirtyProperties.Add(p.Name);
            }
        }
        return updatedEntity;
    }

How much speed am i sacrificing when using this? Does anyone know of a better way to do this?

Thanks in advance

+1  A: 

I'm looking at doing something similar using PostSharp, then comparing the old and new property value when it's being set, and flagging the object as dirty. Shouldn't be too hard to do the same things on a property level.

Chris Doggett
+1  A: 

The only way to know, without doubt, how much speed you're sacrificing would be to profile this.

In general, in my experience, reflecting on properties seems to be, at best, about 1/50th the speed of accessing them directly. At worst it can be 200x slower. Depending on the frequency of this operation, and the number of properties, this may or may not be a noticable difference, though, which again, is why I'd suggest profiling it to tell if you need a different solution.

Reed Copsey
+2  A: 

You are asking 2 questions:

  1. How much speed are you loosing?
  2. Is there a faster way to do it

Question #1:

The answer to the first one is: it depends. Writing the property checking code by hand could be several times faster than the reflection code. However, that might not be actually be a problem depending on how often the code gets called. If the code isn't called very frequently, then you wouldn't get much for the trouble of optimizing it. If, however, it's called a lot, then optimizing it may give you big speed improvements. I would run your app underneath a profiler (I like Jet Brain's Dot Trace personally) to see where the time is actually being spent. The percentage of time spent inside "GenerateDirtyPropertiesOnEntity" will give you the theoretical maximum perf gain you can get by optimizing the method. If that ends up being a small percentage, then I would just keep the code as is.

Question #2

I can think of 2 simple ways of making this faster:

  1. Write the property comparison code by hand.
  2. Use the DynamicMethod class to generate the comparison code

I'm assuming you don't want to do #1. I'll post some code that shows #2 in a second.

Update:

Here's the code for generating a dynamic method

class Util
{
    public static Func<T,T, List<string>> CreateDitryChecker<T>()
    {
        var dm = 
            new DynamicMethod
            (
                "$dirty_checker", 
                typeof(List<string>), 
                new[] { typeof(T), typeof(T) }, 
                typeof(T)
            );

        var ilGen = dm.GetILGenerator();

        //var retVar = new List<string>();
        var retVar = ilGen.DeclareLocal(typeof(List<string>));
        ilGen.Emit(OpCodes.Newobj, typeof(List<string>).GetConstructor(new Type[0]));
        ilGen.Emit(OpCodes.Stloc, retVar);

        var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

        MethodInfo objEqualsMehtod = typeof(object).GetMethod("Equals", new[] { typeof(object) });
        MethodInfo listAddMethod = typeof(List<string>).GetMethod("Add");

        foreach (PropertyInfo prop in properties)
        {
            //Inject code equivalent to the following into the method:

            //if (arg1.prop == null)
            //{
            //     if (arg2.prop != null)
            //     {
            //         retVar.Add("prop")
            //     }
            //}
            //else
            //{
            //    if (! arg1.prop.Equals(arg2))
            //    {
            //        retVar.Add("prop")    
            //    }
            //}
            Label endLabel = ilGen.DefineLabel();
            Label elseLabel = ilGen.DefineLabel();

            //if arg1.prop != null, goto elseLabel
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Call, prop.GetGetMethod());
            ilGen.Emit(OpCodes.Brtrue, elseLabel);

            //if arg2.prop != null, goto endLabel
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
            ilGen.Emit(OpCodes.Brfalse, endLabel);

            //retVar.Add("prop");
            ilGen.Emit(OpCodes.Ldloc, retVar);
            ilGen.Emit(OpCodes.Ldstr, prop.Name);
            ilGen.EmitCall(OpCodes.Callvirt, listAddMethod, null);
            ilGen.Emit(OpCodes.Br, endLabel);

            //elseLabel:
            ilGen.MarkLabel(elseLabel);

            //if (arg0.prop.Equals(arg1.prop), goto endLabel
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
            ilGen.Emit(OpCodes.Ldarg_1);
            ilGen.EmitCall(OpCodes.Call, prop.GetGetMethod(), null);
            ilGen.EmitCall(OpCodes.Callvirt, objEqualsMehtod, null);
            ilGen.Emit(OpCodes.Brtrue, endLabel);

            //retVar.Add("prop")
            ilGen.Emit(OpCodes.Ldloc, retVar);
            ilGen.Emit(OpCodes.Ldstr, prop.Name);
            ilGen.EmitCall(OpCodes.Callvirt, listAddMethod, null);

            //endLAbel:
            ilGen.MarkLabel(endLabel);
        }

        ilGen.Emit(OpCodes.Ldloc, retVar);
        ilGen.Emit(OpCodes.Ret);


        return (Func<T, T, List<string>>) dm.CreateDelegate(typeof(Func<T, T, List<string>>));
    }
}

It takes in a generic parameter T and returns a delegate that, when given 2 T instances will return a list of all the changed properties.

To get a perf boost out of it, it's a good idea to call the method once and store the result in a readonly static field. Something like this would work:

class FooBar { static readonly Func> s_dirtyChecker;

   static FooBar()
   {
       s_dirtyChecker = Util.CreateDirtyChecker<FooBar>();
   }

   public List<string> GetDirtyProperties(Foobar other)
   {
       return s_dirtyChecker(this, other);
   }

}

Scott Wisniewski
Thanks scott. Looking forward to he code sample.
AlteredConcept
It's taking a little while to debug.. I't should be posted in about 1 hour
Scott Wisniewski
No problem. Thanks for taking the time!
AlteredConcept
I added the code.
Scott Wisniewski
You said "2 simple ways"... How is "write a DynamicMethod" simple? It's cool, but writing IL in code is not simple as it is just a few steps above assembly.
Erich Mirabal
Conceptually the whole thing is pretty simple. It's just a few if statements repeated a few times.However, The way it's surfaced is ugly, which makes it look more complex than it really is.It would be nice if C# had support for meta-programing, and allowed this type of thing to be done with good looking concise code. But it doesn't, and so the choices are either:1) Don't be performant2) Write the code by hand (which is what the INotify... stuff does)3) Use DynamicMethod
Scott Wisniewski
I agree the concept is simple, but the implementation is really the important part I think... and it is the devil (as evidenced by your need to take over an hour to write it up and debug it). I wonder how much of this you could do using Expression Trees (which is pretty close to meta-programming, from what I think your definition is). Of course, that is not exactly simple either.
Erich Mirabal
Expression trees don't support statements, so they wouldn't help much here. For me, an hour isn't that long, particularly if it saves many hours down the road. The whole point of the method is to avoid tedious duplication.
Scott Wisniewski
Hi Scott. Thanks for the code.I had a question regarding the the foreach PropertyInfo Loop.what exactly is arg1.prop and arg2.prop? Also does the constructor have to be static? I already have a constructor with a bunch of params.
AlteredConcept
arg1 and arg2 are the parameters to the delegate. arg1.prop was my shorthand for "access the current property (in the loop) from arg1".Static constructors are separate from instance constructors. They get run automatically by the CLR on your behalf, and don't impact how you create objects. You can still use your existing instance constructors.The static constructor makes sure that the dynamic method only gets created once.
Scott Wisniewski
+3  A: 

You might get better performance by trying something using the INotifyPropertyChanged interface. Instead of using reflection you can use event based modeling to accomplish the same thing.

CSLA.NET is an example of a framework that takes that approach.

Example

T SomeProperty()
{
    get
    {
       return _someProperty;
    }
    set
    {
       if (_someProperty <> value)
       {
          _someProperty = value;
          OnPropertyChanged("SomeProperty");
       }
    }
}

and then OnPropertyChanged would look something like

OnPropertyChanged(object params)
{
   DirtyProperties.Add(params);
}

Keep in mind this is total air code. I can't remember how the params were constructed, but it's not actually of type object and the name of the property was included which is how you would determine which property to add to your DirtyProperties list.

Joseph
Thanks Joseph. I'll look into this.
AlteredConcept
No problem, happy coding!
Joseph
+1 For Great Minds... This is exactly what I would've answered.
Erich Mirabal
@Erich +1 to you for letting me know I'm not crazy, or at least not crazy without company =P
Joseph