How to unit test an event subscribtion with NUnit - c#

Hi there i wonder if someone knows an answer to my question:
Consider the following code
class TheHandler
{
...
Public EventHandler myRealWorldEvent;
...
}
class TheSubscriber
{
private TheHandler myHandler = new TheHandler();
public subscribeToHandler()
{
myHandler.myRealWorldEventHandler += OnSomethingHappens;
}
...
pirvate OnSomeThingHappens()
{
...
}
}
My question here is -> how can i test (with NUnit only) that OnSomethingHappens got subscribed to myRealWorldEventHandler. I cannot change the SUT/production-code and i cannot Mock (Moq/Nmock etc.). Does anyone know a solution to my problem?
Best regards,
zhengtonic

NUnit doesn't do that - testing whether some private handler subscribed to some private field. There's too much private stuff involved. However, it's nothing you can't do with a little bit help from reflection. Note that it's not pretty code:
var subscriber = new TheSubscriber();
var handlerField = typeof(TheSubscriber)
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
// if field with such name is not present, let it fail test
.First(f => f.Name == "myHandler");
var handlerInstance = handlerField.GetValue(subscriber);
var someEventField = typeof(TheHandler)
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.First(f => f.Name == "myRealWorldEvent");
var eventInstance = (EventHandler) someEventField.GetValue(handlerInstance);
var subscribedMethod = eventInstance
.GetInvocationList()
.FirstOrDefault(d => d.Method.Name == "OnSomethingHappens");
Assert.That(subscribedMethod, Is.Not.Null);
If you'll have to deal with lot of legacy systems testing (ie. private members, static members - something that free frameworks don't handle well or at all) - I suggest taking a look at tools such as TypeMock or JustMock.

Had the same problem. Code from jimmy_keen was not working in a proper way with old .NETs. Solved it by writing helper methods:
public static void assertSubscribed<EventHandlerType>(object handler, object subscriber, string eventName = null) {
var inappropriate = false;
try {
if (!typeof (EventHandlerType).IsSubclassOf(typeof (Delegate)) ||
typeof (EventHandlerType).GetMethod("Invoke").ReturnType != typeof (void))
inappropriate = true;
} catch (AmbiguousMatchException) {
inappropriate = true;
} finally {
if (inappropriate) throw new Exception("Inappropriate Delegate: " + typeof (EventHandlerType).Name);
}
var handlerField = subscriber.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.First(h => h.FieldType.IsInstanceOfType(handler));
var handlerInstance = handlerField == null ? null : handlerField.GetValue(subscriber);
var eventField = handlerInstance == null ? null : handlerInstance.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.First(f => (f.FieldType.IsAssignableFrom(typeof (EventHandlerType)) &&
(eventName == null || eventName.Equals(f.Name))));
var eventInstance = eventField == null ? null : (Delegate)eventField.GetValue(handlerInstance);
var subscribedMethod = eventInstance == null
? null
:eventInstance.GetInvocationList().FirstOrDefault(
d => d.Method.DeclaringType != null && d.Method.DeclaringType.IsInstanceOfType(subscriber));
Assert.That(subscribedMethod, Is.Not.Null);
}
"Not" method:
public static void assertNotSubscribed<EventHandlerType>(object handler, object subscriber, string eventName = null) {
var inappropriate = false;
try {
if (!typeof (EventHandlerType).IsSubclassOf(typeof (Delegate)) ||
typeof (EventHandlerType).GetMethod("Invoke").ReturnType != typeof (void))
inappropriate = true;
} catch (AmbiguousMatchException) {
inappropriate = true;
}
if (inappropriate) return;
var handlerField = subscriber.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.First(h => h.FieldType.IsInstanceOfType(handler));
var handlerInstance = handlerField == null ? null : handlerField.GetValue(subscriber);
var eventField = handlerInstance == null ? null : handlerInstance.GetType()
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.First(f => (f.FieldType.IsAssignableFrom(typeof (EventHandlerType)) &&
(eventName == null || eventName.Equals(f.Name))));
var eventInstance = eventField==null?null:(Delegate) eventField.GetValue(handlerInstance);
var subscribedMethod = eventInstance == null
? null
: eventInstance.GetInvocationList().FirstOrDefault(
d => d.Method.DeclaringType != null && d.Method.DeclaringType.IsInstanceOfType(subscriber));
Assert.That(subscribedMethod, Is.Null);
}
And invocation:
assertSubscribed<EventHandler>(handler, subscriber);
assertNotSubscribed<EventHandler>(handler, subscriber);
assertSubscribed<EventHandler>(handler, subscriber, "myRealWorldEvent");
assertNotSubscribed<EventHandler>(handler, subscriber, "myRealWorldEvent");
Don't bother me for code style convention but this method looks compact enough.

Related

i updated my project from dotnet 2.1 to 6 and some base repository function return 500

error in debugger
im trying to do sothing like this but need help with how
upgrate my project from dotnet 2.1 to 6
my base function now return 500
using vscode
i need to check if IsActive property is exist in that model from base repo that im using generic model
public virtual async Task<IEnumerable<TPickerDto>> GetForPickerAsync([Optional] List<string> fieldsForInclude, [Optional] Dictionary<string, ItemsPermissions> permissions)
{
IQueryable<TModel> listFromDb = this._context.Set<TModel>()
.Where(r => (r.GetType().GetProperty("IsActive") != null &&
r.GetType().GetProperty("IsActive").GetValue(r) != null &&
(bool)r.GetType().GetProperty("IsActive").GetValue(r) == true) ||
(r.GetType().GetProperty("IsActive") == null) ||
(r.GetType().GetProperty("IsActive").GetValue(r) == null));
if (permissions != null)
{
if (permissions.ContainsKey("allowedUnits") && permissions.ContainsKey("allowedSites"))
{
if (permissions.GetValueOrDefault("allowedUnits").All && !permissions.GetValueOrDefault("allowedSites").All)
{
listFromDb = listFromDb.Where(r => r.GetType().GetProperty("SiteId").GetValue(r) == null
|| permissions.GetValueOrDefault("allowedSites").Indexes.Contains((int)r.GetType().GetProperty("SiteId").GetValue(r)));
}
else
{
if (!permissions.GetValueOrDefault("allowedUnits").All && permissions.GetValueOrDefault("allowedSites").All)
{
listFromDb = listFromDb.Where(r => r.GetType().GetProperty("UnitId").GetValue(r) == null
|| permissions.GetValueOrDefault("allowedUnits").Indexes.Contains((int)r.GetType().GetProperty("UnitId").GetValue(r)));
}
else
{
if (!permissions.GetValueOrDefault("allowedUnits").All && !permissions.GetValueOrDefault("allowedSites").All)
{
listFromDb = listFromDb.Where(r => (r.GetType().GetProperty("UnitId").GetValue(r) == null
|| permissions.GetValueOrDefault("allowedUnits").Indexes.Contains((int)r.GetType().GetProperty("UnitId").GetValue(r)))
&& (r.GetType().GetProperty("SiteId").GetValue(r) == null
|| permissions.GetValueOrDefault("allowedSites").Indexes.Contains((int)r.GetType().GetProperty("SiteId").GetValue(r))));
}
}
}
}
else
{
if (permissions.ContainsKey("allowedUnits"))
{
listFromDb = listFromDb
.Where(r => permissions.GetValueOrDefault("allowedUnits").All
|| r.GetType().GetProperty("UnitId").GetValue(r) == null
|| permissions.GetValueOrDefault("allowedUnits").Indexes.Contains((int)r.GetType().GetProperty("UnitId").GetValue(r)));
}
if (permissions.ContainsKey("allowedSites"))
{
listFromDb = listFromDb
.Where(r => permissions.GetValueOrDefault("allowedSites").All
|| r.GetType().GetProperty("SiteId").GetValue(r) == null
|| permissions.GetValueOrDefault("allowedSites").Indexes.Contains((int)r.GetType().GetProperty("SiteId").GetValue(r)));
}
}
}
// Import all the navigation properties
if (fieldsForInclude != null)
{
listFromDb = _JoinNavigationProperties(listFromDb, fieldsForInclude);
}
IEnumerable<TPickerDto> mappingList = _mapper.Map<IEnumerable<TPickerDto>>(listFromDb);
return mappingList;
}
Starting from dotnet core 3, Ef will throw an exception if a Linq query couldn't be translated to SQL and results in Client-side evaluation. In earlier versions you would just receive a warning. You will need to improve your Linq query so that it can be evaluated on client side.
Refer to this link for details,
https://learn.microsoft.com/en-us/ef/core/querying/client-eval
You are using reflection to do the query, why not use the property directly? If your boolean can be be NULL then declare the type as bool? IsActive. That way you can check for null in the where,
this._context.Set<TModel>().Where(r => (r.IsActive==true))
In case you are trying to create a repository then try declaring an interface that has IsActive as a field.
interface ISoftDeleteTarget
{
public IsActive{get;}
}

The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier

In following LinQ query to get Phone Number I'm calling another async method GetAspNetUserPhoneNumberByAccountId, which throws this error
Error CS4034 The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier.
Anyone have idea about it ?
var fullAppointment = await Task.Run(() => Context.AppointmentDetail
.Where(u =>
u.StartDateTime >= startdatetime
&& u.EndDateTime <= enddatetime
)
.Select(x => new Contracts.CalenderModel2()
{
StatusId = (Contracts.Enum.EnumWOStatus)x.Status,
FName = x.Appointment != null ? x.Appointment.Customer.Account.FName : "",
LName = x.Appointment != null ? x.Appointment.Customer.Account.LName : "",
**PrimaryPhone = x.Appointment != null ?
(await _userRepository.GetAspNetUserPhoneNumberByAccountId( x.Appointment.Customer.AccountId))**
: "",
Year = x.Appointment != null && x.Appointment.Vehicle != null ? x.Appointment.Vehicle.MakeYear.Year : 0,
Make = x.Appointment != null && x.Appointment.Vehicle != null ? x.Appointment.Vehicle.VehicleMaker.MakerName : "",
Model = x.Appointment != null && x.Appointment.Vehicle != null ? x.Appointment.Vehicle.VehicleModel.Model : "",
AppointmentId = x.AppointmentId,
JobEndDateTime = x.EndDateTime,
JobStartDateTime = x.StartDateTime,
ColorCategory = x.AppointmentType.ColorCategory,
SalesRepersentativeUserId =
(x.Appointment != null && x.Appointment.Customer.CustomerBillTo.Count > 0)
? x.Appointment.Customer.CustomerBillTo.FirstOrDefault().BillToId : Guid.Empty,
FullAppointmentDetail = new Contracts.FullAppointmentDetail
{
BayId = x.BayId,
BayName = x.WorkArea != null ? x.WorkArea.BayName : "",
WorkTypeId = x.WorkTypeId,
WorkTypeName = x.WorkType != null ? x.WorkType.WorkTypeName : "",
JobId = x.Appointment != null && x.Appointment.Job != null ? x.Appointment.Job.Id : Guid.Empty,
JobIdInt = x.Appointment != null && x.Appointment.Job != null ? x.Appointment.Job.JobIdInt : 0,
AssigneeUserId = x.AssigneeUserId,
WorkOrderId = x.WorkOrders.FirstOrDefault() != null ? x.WorkOrders.FirstOrDefault().Id : Guid.Empty
}
})
.ToList());
The Definition of GetAspNetUserPhoneNumberByAccountId is given below
public async Task<string> GetAspNetUserPhoneNumberByAccountId(Guid accountId)
{
var phone = await Task.Run(() => _Context.Account.Where(ac => ac.Id.Equals(accountId))
.Join(_Context.AspNetUsers, ac => ac.AspNetUserId, u => u.Id, (ac, u) => new
{
PhoneNumber = u.PhoneNumber,
}).FirstOrDefault());
return phone!=null?phone.ToString():"";
}
I agree with guys in the comments that you don't need to use async method in this query. In additional this query will not optimize. For each item in Where condition you will do it query to DB. If you have a lot of data, performance of DB will go down. I think in this case much better to do it 2 queries to DB. For getting AppointmentDetail and another query for getting all accounts. And both query call with async. And after that combine it to your Contracts.CalenderModel2 class.
It will be more readable and work faster.
And don't use Task.Run();

Roslyn - Rewrite extension method invocation to full qualified format

How can I rewrite an invocation of an extension method to the full qualified format, that doesn't need a using anymore?
For example.
list.OrderBy(x => x.Prop)
should become
System.Collections.Generic.Enumerable.OrderBy(list, x => x.Prop)
Answering my own question. I use this in a CSharpSyntaxRewriter object.
It would be nice if somebody also could have a look at this.
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
{
var methodInfo = _model.GetSymbolInfo(node).Symbol as IMethodSymbol;
if (methodInfo != null)
{
var containingType = methodInfo.ContainingType;
if (methodInfo.IsExtensionMethod)
{
// extension method
var exp = node.Expression as MemberAccessExpressionSyntax;
if (exp != null)
{
// new arguments
var newArguments = SyntaxFactory.ArgumentList();
newArguments = newArguments.AddArguments(SyntaxFactory.Argument(exp.Expression));
newArguments = newArguments.AddArguments(node.ArgumentList.Arguments.ToArray());
// new expression
var stack = new Stack<SimpleNameSyntax>();
stack.Push(exp.Name);
stack.Push(SyntaxFactory.IdentifierName(containingType.Name));
for (var s = containingType.ContainingNamespace; s != null; s = s.ContainingNamespace)
{
if (!s.IsGlobalNamespace && s.Name != "")
stack.Push(SyntaxFactory.IdentifierName(s.Name));
}
// build and return
if (stack.Count >= 2)
{
var newExp = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, stack.Pop(), stack.Pop());
while (stack.Any())
newExp = SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, newExp, stack.Pop());
return node.WithExpression(newExp).WithArgumentList(newArguments);
}
}
}
}
return base.VisitInvocationExpression(node);
}

ASP.NET MVC5 Entity Framework 6 get bool = true and bool = false LINQ

I have a table that I am filtering on.
There is a filter for values 'include' which can be true or false.
I have a filter that has 3 options: true, false, & all.
So, when the filter is true, it should return rows where include = 'true'; when the filter is 'false', return where include = false; and when 'all' return where include = true or false.
Here is my code, that is not working, but I think it should be.
private ICollection<AggregationEntityViewModel> getEntities(AggregationPracticeDetailsViewModel apdvm)
{
bool? filterInclude = Convert.ToBoolean(apdvm.Filter_IncludeValue);
var a = (from e in _repository.GetAll<Entity>()
where e.include == filterInclude != null ? (bool)filterInclude : (true || false)
select e
return a;
}
It is currently returning 0 rows when filter is set to 'All' or 'False', and returning all rows when set to 'Yes'.
FYI, I have ommitted lots of code for clarity's sake.
Please help...thanks!
*EDIT: I've displayed all the code, so you can see why I want to keep it all in linq query. Thanks for all the offered solutions. I see that most solutions involve using Linq Extension methods. Is there anyway to do it in inline linq query? *
bool? filterInclude = Convert.ToBoolean(apdvm.Filter_IncludeValue);
var a = (from e in _repository.GetAll<Entity>()
from u in e.Users
where (e.AuditQuestionGroupId != null ? e.AuditQuestionGroupId : 0) == this.LoggedInEntity.AuditQuestionGroupId
&& e.BatchNumber != null && e.BatchNumber.StartsWith(apdvm.Filter_BatchNumber == null ? "" : apdvm.Filter_BatchNumber)
&& e.Name != null && e.Name.ToLower().StartsWith(apdvm.Filter_EntityName.ToLower())
&& e.EntityState != null && e.EntityState.ToLower().Contains(apdvm.Filter_StateValue == null ? "" : apdvm.Filter_StateValue.ToLower())
&& u.NIAMembershipId != null && u.NIAMembershipId.Contains(apdvm.Filter_MemberNo == null ? "" : apdvm.Filter_MemberNo)
from p in e.PracticeProfiles.DefaultIfEmpty()
join ea in _repository.GetAll<EntityAggregate>() on e.EntityId equals ea.EntityId into eas
from ea in eas.DefaultIfEmpty()
where ea.include == filterInclude != null ? (bool)filterInclude : (true || false)
group e by new { entity = e, profile = p, ea = ea } into newGroup
orderby newGroup.Key.entity.Name
select new AggregationEntityViewModel()
{
Id = newGroup.Key.ea == null ? 0 : newGroup.Key.ea.Id,
EntityId = newGroup.Key.entity.EntityId,
Include = newGroup.Key.ea == null ? (true || false) : (bool)newGroup.Key.ea.include,
BHAddress = newGroup.Key.profile == null || newGroup.Key.profile.soloOffice == null ? false : (bool)newGroup.Key.profile.soloOffice,
Incorporated = newGroup.Key.profile == null || newGroup.Key.profile.company == null ? false : (bool)newGroup.Key.profile.company,
MajorityOwned = newGroup.Key.profile == null || newGroup.Key.profile.capital == null ? false : (bool)newGroup.Key.profile.capital,
MajorityVoting = newGroup.Key.profile == null || newGroup.Key.profile.votingRights == null ? false : (bool)newGroup.Key.profile.votingRights,
Name = newGroup.Key.entity.Name,
Partnership = newGroup.Key.profile == null || newGroup.Key.profile.partnership == null ? false : (bool)newGroup.Key.profile.partnership,
PublicAccountant = newGroup.Key.profile == null || newGroup.Key.profile.publicAccountant == null ? false : (bool)newGroup.Key.profile.publicAccountant,
Trust = newGroup.Key.profile == null || newGroup.Key.profile.operatingTrust == null ? false : (bool)newGroup.Key.profile.operatingTrust,
TrustDeed = newGroup.Key.profile == null || newGroup.Key.profile.deed == null ? false : (bool)newGroup.Key.profile.deed
}).ToList();
return a;
Convert.ToBoolean returns bool, not bool?, so there is no way filterInclude != null is true.
You should use following pattern instead of ternary operator within where clause:
var query = _repository.GetAll<Entity>();
if (apdvm.Filter_IncludeValue == "true")
query = query.Where(x => x.include == true);
else if (apdvm.Filter_IncludeValue == "false")
query = query.Where(x => x.include == false);
return query;
I assumed apdvm.Filter_IncludeValue is a string (and that's why you tried to call Convert.ToBoolean on it).
You could use
private ICollection<AggregationEntityViewModel> getEntities(
AggregationPracticeDetailsViewModel apdvm)
{
bool? filterInclude = apdvm.Filter_IncludeValue.ConvertToNullable<bool>();
var a = (from e in _repository.GetAll<Entity>()
where !filterInclude.HasValue || ea.include == filterInclude.Value
select new AggregationEntityViewModel()
{
Include = newGroup.Key.ea == null
? (true || false)
: (bool)newGroup.Key.ea.include,
}
return a;
}
just remove your (true||false) and add filterInclude == null in the where
For Nullable Value (taken from Convert string to nullable type (int, double, etc...))
public static T? ConvertToNullable<T>(this String s) where T : struct
{
try
{
return (T?)TypeDescriptor.GetConverter(typeof(T)).ConvertFrom(s);
}
catch (Exception)
{
return null;
}
}
There is an other solution:
var query = from e in _repository.GetAll<Entity>();
if (filterInclude.HasValue)
{
// when filterInclude is null (it means **ALL**),
// do not filter otherwise - check the flag
query = query.Where(entity => entity.Include == filterInclude.Value);
}
// or one-line:
// query = query.Where(entity => filterInclude == null
// || entity.Include == filterInclude.Value);
var a = query.Select(entity => new AggregationEntityViewModel { .... });
return a;
Other problem is that Convert.ToBoolean never returns null. You should create own method to parse apdvm.Filter_IncludeValue.
In order to convert to nullable type, you colud use the generic method:
public static Nullable<T> ToNullable<T>(this string s) where T: struct
{
Nullable<T> result = new Nullable<T>();
try
{
if (!string.IsNullOrEmpty(s) && s.Trim().Length > 0)
{
TypeConverter conv = TypeDescriptor.GetConverter(typeof(T));
result = (T)conv.ConvertFrom(s);
}
}
catch { }
return result;
}
Source.
Usage:
var filterInclude = apdvm.Filter_IncludeValue.ToNullable<bool>();
You can make it easier with fluent syntax like this:
private ICollection<AggregationEntityViewModel> getEntities(AggregationPracticeDetailsViewModel apdvm)
{
var query = _repository.GetAll<Entity>();
if(apdvm.Filter_IncludeValue != 'all')
{
var value = Convert.ToBoolean(apdvm.Filter_IncludeValue);
query = query.Where(q => q.include == value)
}
return query.Select(q => new AggregationEntityViewModel {...}).ToArray();
}
no need to evaluate string to nullable bool or smth. Same as no need to do strange boolean expressions.

fastest way to compare 2 objects excluding a few properties?

I have a site where users upload data into it and I only want to update data where properties have been changed. So I am comparing 2 objects of the same type for changes and I need to exclude a few properties such as ModifiedOn which is a date.
Here is my code thus far using reflection:
private bool hasChanges(object OldObject, object newObject)
{
var oldprops = (from p in OldObject.GetType().GetProperties() select p).ToList();
var newprops = (from p in newObject.GetType().GetProperties() select p).ToList();
bool isChanged = false;
foreach (PropertyInfo i in oldprops)
{
if (checkColumnNames(i.Name))
{
var newInfo = (from x in newprops where x.Name == i.Name select x).Single();
var oldVal = i.GetValue(OldObject, null);
var newVal = newInfo.GetValue(newObject, null);
if (newVal == null || oldVal == null)
{
if (newVal == null && oldVal != null)
{
isChanged = true;
return true;
}
if (oldVal == null && newVal != null)
{
isChanged = true;
return true;
}
}
else
{
if (!newVal.Equals(oldVal))
{
isChanged = true;
return true;
}
}
}
}
return isChanged;
}
I ignore certain columns with this method:
private bool checkColumnNames(string colName)
{
if (
colName.ToLower() == "productid" ||
colName.ToLower() == "customerid" ||
colName.ToLower() == "shiptoid" ||
colName.ToLower() == "parentchildid" ||
colName.ToLower() == "categoryitemid" ||
colName.ToLower() == "volumepricingid" ||
colName.ToLower() == "tagid" ||
colName.ToLower() == "specialprice" ||
colName.ToLower() == "productsmodifierid" ||
colName.ToLower() == "modifierlistitemid" ||
colName.ToLower() == "modifierlistid" ||
colName.ToLower() == "categoryitemid" ||
colName.ToLower() == "createdon" ||
colName.ToLower() == "createdby" ||
colName.ToLower() == "modifiedon" ||
colName.ToLower() == "modifiedby" ||
colName.ToLower() == "deletedon" ||
colName.ToLower() == "deletedby" ||
colName.ToLower() == "appendproductmodifiers" ||
colName.ToLower() == "introdate" ||
colName.ToLower() == "id" ||
colName.ToLower() == "discontinued" ||
colName.ToLower() == "stagingcategories"
)
return false;
return true;
}
This has been working very well except for now I have users comparing 50,000+ items in a single upload which is taking a really long time.
Is there a faster way to accomplish this?
Compile and cache your code using expression trees or dynamic methods. You will probably see a 10-100x performance improvement. Your original reflection code to retrieve properties and you can use it as a basis for creating a compiled version.
Example
Here is a snippet of code I use in a framework for reading all of the properties of an object to track state changes. In this scenario, I do not know any of the property names of the object. All of the property values are placed into a StringBuilder.
I've simplified this from my original code; it still compiles, but you may need to tweak it.
private static DynamicMethod CreateChangeTrackingReaderIL( Type type, Type[] types )
{
var method = new DynamicMethod( string.Empty, typeof( string ), new[] { type } );
ILGenerator il = method.GetILGenerator();
LocalBuilder lbInstance = il.DeclareLocal( type );
// place the input parameter of the function onto the evaluation stack
il.Emit( OpCodes.Ldarg_0 );
// store the input value
il.Emit( OpCodes.Stloc, lbInstance );
// declare a StringBuilder
il.Emit( OpCodes.Newobj, typeof( StringBuilder ).GetConstructor( Type.EmptyTypes ) );
foreach( Type t in types )
{
// any logic to retrieve properties can go here...
List<PropertyInfo> properties = __Properties.GetTrackableProperties( t );
foreach( PropertyInfo pi in properties )
{
MethodInfo mi = pi.GetGetMethod();
if( null == mi )
{
continue;
}
il.Emit( OpCodes.Ldloc, lbInstance ); // bring the stored reference onto the eval stack
il.Emit( OpCodes.Callvirt, mi ); // call the appropriate getter method
if( pi.PropertyType.IsValueType )
{
il.Emit( OpCodes.Box, pi.PropertyType ); // box the return value if necessary
}
// append it to the StringBuilder
il.Emit( OpCodes.Callvirt, typeof( StringBuilder ).GetMethod( "Append", new Type[] { typeof( object ) } ) );
}
}
// call ToString() on the StringBuilder
il.Emit( OpCodes.Callvirt, typeof( StringBuilder ).GetMethod( "ToString", Type.EmptyTypes ) );
// return the last value on the eval stack (output of ToString())
il.Emit( OpCodes.Ret );
return method;
}
Note that if you are not familiar with IL generation, most people find expression trees much easier to work with. Either approach has a similar result.
It would certainly be faster if you just used reflection to create and compile a method using the above logic. This should be much faster than reflecting on each object.
Are the objects guaranteed to have the same type? Even if not, you could check them, and if they are the same type, send them to this method:
private bool hasChanges(object OldObject, object newObject)
{
var props = OldObject.GetType().GetProperties();
foreach (PropertyInfo i in props)
{
if (checkColumnNames(i.Name))
{
var oldVal = i.GetValue(OldObject, null);
var newVal = i.GetValue(newObject, null);
if (newVal == null)
{
if (oldVal != null)
{
return true;
}
}
else if (oldVal == null)
{
return true;
}
else if (!newVal.Equals(oldVal))
{
return true;
}
}
}
return false;
}
This is only slightly more efficient than your method. As Tim Medora and PinnyM noted, it would be quicker still to emit code dynamically and cache the result, which would mean that you take the reflection hit only once, rather than once per object.
Note also that according to Best Practices for Using Strings in the .NET Framework, you should be using ToUpper rather than ToLower for your string comparisons, but you should be using String.Equals(string, string, StringComparison) rather than converting the case yourself. That will have one advantage, at least: Equals returns false if the strings are different lengths, so you skip the case conversion. That will save a bit of time as well.
Another thought:
If the objects are of different types, you can still improve your algorithm by joining the properties collections rather than using the repeated linear search you have:
private bool hasChanges(object OldObject, object newObject)
{
var oldprops = OldObject.GetType().GetProperties();
var newprops = newObject.GetType().GetProperties();
var joinedProps = from oldProp in oldprops
join newProp in newProps
on oldProp.Name equals newProp.Name
select new { oldProp, newProp }
foreach (var pair in joinedProps)
{
if (checkColumnNames(pair.oldProp.Name))
{
var oldVal = pair.oldProp.GetValue(OldObject, null);
var newVal = pair.newProp.GetValue(newObject, null);
//etcetera
Final thought (inspired by Tim Schmelter's comment):
Override object.Equals on your classes, and use
private bool HasChanges(object o1, object o2) { return !o1.Equals(o2); }
Sample class:
class SomeClass
{
public string SomeString { get; set; }
public int SomeInt { get; set; }
public DateTime SomeDateTime { get; set; }
public bool Equals(object other)
{
SomeClass other1 = other as SomeClass;
if (other1 != null)
return other1.SomeInt.Equals(SomeInt)
&& other1.SomeDateTime.Equals(SomeDateTime)
&& other1.SomeString.Equals(SomeString); //or whatever string equality check you prefer
//possibly check for other types here, if necessary
return false;
}
}

Categories

Resources