Passing FieldNames and Values dynamically to linq - c#

I want to be able to pass FieldName as a string into Method that updates a table. I have looked at Linq.Expressions but this doesn't seem to expose all the details I need about the field Name and dataType and I don't really want to go through the existing string builder solution that executes a sql command directly.
public static Main(string[] args)
{
UpdateLicenceField(2284, "laststoragefullalert", DateTime.Now);
UpdateLicenceField(2284, "numberofalerts", int(tooMany);
UpdateLicenceField(2284, "lastalertmessage", "Oops");
}
public static void UpdateLicenceField(int LicenceID, string FieldName, object value)
{
using (myContext db = new MyContext())
{
Licence licence = db.Licence.Where(x => x.ID == LicenceID &&
x.Deleted == false).FirstOrDefault();
// so db.Licence has fields .laststoragefullalert .numberofalerts .lastalertmessage
// (in real life this has hundred of settings and we very often just want to update one)
// I'm tring to get a single Method that will act dynamically like the current ADO function that creates a SQL string and executes that.
// 1. check that the FieldName type is the same type as the object passed in value.
// 2. update that FieldName with value and Saves the Licence table.
}
}

You can use "System.Linq.Dynamic.Core" library from https://github.com/StefH/System.Linq.Dynamic.Core and use:
Licence licence = db.Licence.Where(FieldName + "==#0", fieldValue).FirstOrDefault();
or use a method like this:
public Expression<Func<TEntity, bool>> GetPredicate(string methodName, string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(TEntity), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod(methodName, new[] { typeof(string) });
if (method == null)
{
var containsMethods = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == methodName);
foreach (var m in containsMethods)
{
if (m.GetParameters().Count() == 2)
{
method = m;
break;
}
}
}
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<TEntity, bool>>(containsMethodExp, parameterExp);
}
Licence licence = db.Licence.Where(GetPredicate("Contains", fieldName, fieldValue)).FirstOrDefault();
I hope it helps you.

Thanks very much for your responses. Using a bit from both Renan and Roberto's reply I put something together that does the job. It's obviously not ideal to update in this way but this is instead of having 50 or 60 functions with similar code within an automated process. There's still error checking and validations to be added but this will reduce my codebase considerably.
Thanks again Glenn.
public static int Main(string[] args)
{
UpdateLicenceField(2284, "LastStorageFullAlert", DateTime.Now);
UpdateLicenceField(2284, "IsProcessing", true);
UpdateLicenceField(2284, "RegSource", "this is a string");
UpdateLicenceField(2284, "ProcessFilesDelay", 200);
return 1;
}
public static void UpdateLicenceField(int LicenceID, string Field, object Value)
{
using (BACCloudModel BACdb = new BACCloudModel())
{
Licence licence = BACdb.Licence.Where(x => x.ID == LicenceID && x.Deleted == false).FirstOrDefault();
if (licence != null)
{
var fieldvalue = licence.GetType().GetProperty(Field);
var ftype = fieldvalue.PropertyType;
var vtype = Value.GetType();
if (ftype == vtype)
{
object[] Values = { Value };
licence.GetType().GetProperty(Field).SetMethod.Invoke(licence, Values);
BACdb.SaveChanges();
}
}
}
}

Related

error reflection and Abstract class, call AppDomain.CurrentDomain.GetAssemblies() [duplicate]

I am trying to invoke a method via reflection with parameters and I get:
object does not match target type
If I invoke a method without parameters, it works fine. Based on the following code if I call the method Test("TestNoParameters"), it works fine. However if I call Test("Run"), I get an exception. Is something wrong with my code?
My initial purpose was to pass an array of objects e.g. public void Run(object[] options) but this did not work and I tried something simpler e.g. string without success.
// Assembly1.dll
namespace TestAssembly
{
public class Main
{
public void Run(string parameters)
{
// Do something...
}
public void TestNoParameters()
{
// Do something...
}
}
}
// Executing Assembly.exe
public class TestReflection
{
public void Test(string methodName)
{
Assembly assembly = Assembly.LoadFile("...Assembly1.dll");
Type type = assembly.GetType("TestAssembly.Main");
if (type != null)
{
MethodInfo methodInfo = type.GetMethod(methodName);
if (methodInfo != null)
{
object result = null;
ParameterInfo[] parameters = methodInfo.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
if (parameters.Length == 0)
{
// This works fine
result = methodInfo.Invoke(classInstance, null);
}
else
{
object[] parametersArray = new object[] { "Hello" };
// The invoke does NOT work;
// it throws "Object does not match target type"
result = methodInfo.Invoke(methodInfo, parametersArray);
}
}
}
}
}
Change "methodInfo" to "classInstance", just like in the call with the null parameter array.
result = methodInfo.Invoke(classInstance, parametersArray);
You have a bug right there
result = methodInfo.Invoke(methodInfo, parametersArray);
it should be
result = methodInfo.Invoke(classInstance, parametersArray);
A fundamental mistake is here:
result = methodInfo.Invoke(methodInfo, parametersArray);
You are invoking the method on an instance of MethodInfo. You need to pass in an instance of the type of object that you want to invoke on.
result = methodInfo.Invoke(classInstance, parametersArray);
The provided solution does not work for instances of types loaded from a remote assembly. To do that, here is a solution that works in all situations, which involves an explicit type re-mapping of the type returned through the CreateInstance call.
This is how I need to create my classInstance, as it was located in a remote assembly.
// sample of my CreateInstance call with an explicit assembly reference
object classInstance = Activator.CreateInstance(assemblyName, type.FullName);
However, even with the answer provided above, you'd still get the same error. Here is how to go about:
// first, create a handle instead of the actual object
ObjectHandle classInstanceHandle = Activator.CreateInstance(assemblyName, type.FullName);
// unwrap the real slim-shady
object classInstance = classInstanceHandle.Unwrap();
// re-map the type to that of the object we retrieved
type = classInstace.GetType();
Then do as the other users mentioned here.
I tried to work with all the suggested answers above but nothing seems to work for me. So i am trying to explain what worked for me here.
I believe if you are calling some method like the Main below or even with a single parameter as in your question, you just have to change the type of parameter from string to object for this to work. I have a class like below
//Assembly.dll
namespace TestAssembly{
public class Main{
public void Hello()
{
var name = Console.ReadLine();
Console.WriteLine("Hello() called");
Console.WriteLine("Hello" + name + " at " + DateTime.Now);
}
public void Run(string parameters)
{
Console.WriteLine("Run() called");
Console.Write("You typed:" + parameters);
}
public static string StaticString()
{
return "static string example";
}
public string TestNoParameters()
{
Console.WriteLine("TestNoParameters() called");
return ("TestNoParameters() called");
}
public void Execute(object[] parameters)
{
Console.WriteLine("Execute() called");
Console.WriteLine("Number of parameters received: " + parameters.Length);
for(int i=0;i<parameters.Length;i++){
Console.WriteLine(parameters[i]);
}
}
}
}
Then you have to pass the parameterArray inside an object array like below while invoking it. The following method is what you need to work
private object ExecuteWithReflection(string methodName,object parameterObject = null)
{
Assembly assembly = Assembly.LoadFile("Assembly.dll");
Type typeInstance = assembly.GetType("TestAssembly.Main");
MethodInfo methodInfo = typeInstance.GetMethod(methodName);
ParameterInfo[] parameterInfo = methodInfo.GetParameters();
object result = null;
if (typeInstance != null) //non static
{
if(methodInfo.IsStatic == false)
{
//instance is needed to invoke the method
object classInstance = Activator.CreateInstance(typeInstance, null);
if (parameterInfo.Length == 0)
{
// there is no parameter we can call with 'null'
result = methodInfo.Invoke(classInstance, null);
}
else
{
result = methodInfo.Invoke(classInstance,new object[] { parameterObject } );
}
}
else //handle static
{
if (parameterInfo.Length == 0)
{
// there is no parameter we can call with 'null'
result = methodInfo.Invoke(null, null);
}
else
{
result = methodInfo.Invoke(null,new object[] { parameterObject } );
}
}
}
return result;
}
This method makes it easy to invoke the method, it can be called as following
ExecuteWithReflection("Hello");
ExecuteWithReflection("Run","Vinod");
ExecuteWithReflection("TestNoParameters");
ExecuteWithReflection("Execute",new object[]{"Vinod","Srivastav"});
ExecuteWithReflection("StaticString");
I'am posting this answer because many visitors enter here from google for this problem.
string result = this.GetType().GetMethod("Print").Invoke(this, new object[]{"firstParam", 157, "third_Parammmm" } );
when external .dll -instead of this.GetType(), you might use typeof(YourClass).
I would use it like this, its way shorter and it won't give any problems
dynamic result = null;
if (methodInfo != null)
{
ParameterInfo[] parameters = methodInfo.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
result = methodInfo.Invoke(classInstance, parameters.Length == 0 ? null : parametersArray);
}
Assembly assembly = Assembly.LoadFile(#"....bin\Debug\TestCases.dll");
//get all types
var testTypes = from t in assembly.GetTypes()
let attributes = t.GetCustomAttributes(typeof(NUnit.Framework.TestFixtureAttribute), true)
where attributes != null && attributes.Length > 0
orderby t.Name
select t;
foreach (var type in testTypes)
{
//get test method in types.
var testMethods = from m in type.GetMethods()
let attributes = m.GetCustomAttributes(typeof(NUnit.Framework.TestAttribute), true)
where attributes != null && attributes.Length > 0
orderby m.Name
select m;
foreach (var method in testMethods)
{
MethodInfo methodInfo = type.GetMethod(method.Name);
if (methodInfo != null)
{
object result = null;
ParameterInfo[] parameters = methodInfo.GetParameters();
object classInstance = Activator.CreateInstance(type, null);
if (parameters.Length == 0)
{
// This works fine
result = methodInfo.Invoke(classInstance, null);
}
else
{
object[] parametersArray = new object[] { "Hello" };
// The invoke does NOT work;
// it throws "Object does not match target type"
result = methodInfo.Invoke(classInstance, parametersArray);
}
}
}
}
I m invoking the weighted average through reflection. And had used method with more than one parameter.
Class cls = Class.forName(propFile.getProperty(formulaTyp));// reading class name from file
Object weightedobj = cls.newInstance(); // invoke empty constructor
Class<?>[] paramTypes = { String.class, BigDecimal[].class, BigDecimal[].class }; // 3 parameter having first is method name and other two are values and their weight
Method printDogMethod = weightedobj.getClass().getMethod("applyFormula", paramTypes); // created the object
return BigDecimal.valueOf((Double) printDogMethod.invoke(weightedobj, formulaTyp, decimalnumber, weight)); calling the method
On .Net 4.7.2 to invoke a method inside a class loaded from an external assembly you can use the following code in VB.net
Dim assembly As Reflection.Assembly = Nothing
Try
assembly = Reflection.Assembly.LoadFile(basePath & AssemblyFileName)
Dim typeIni = assembly.[GetType](AssemblyNameSpace & "." & "nameOfClass")
Dim iniClass = Activator.CreateInstance(typeIni, True)
Dim methodInfo = typeIni.GetMethod("nameOfMethod")
'replace nothing by a parameter array if you need to pass var. paramenters
Dim parametersArray As Object() = New Object() {...}
'without parameters is like this
Dim result = methodInfo.Invoke(iniClass, Nothing)
Catch ex As Exception
MsgBox("Error initializing main layout:" & ex.Message)
Application.Exit()
Exit Sub
End Try

Async await doesn't work with dynamic parameter [duplicate]

This question already has answers here:
Passing dynamic object to C# method changes return type
(2 answers)
Closed 3 years ago.
I have a problem passing dynamic parameter to the async method. Compile time error says Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable<dynamic>' to 'System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task>'.
Actually, I am trying to log entries for each DB table column entries. So, I am using a generic method which takes dynamic oldervalue and newvalue and perform insertion into the AuditDB.
Code
public async Task Translog(int Id, string Action, DateTime RecordTimeStamp, dynamic oldervalue, dynamic newvalue, VIBRANT db, VibrantAuditEntities context)
{
try
{
#region FrameLog
Type typeOfMyObject;
if (oldervalue != null)
typeOfMyObject = oldervalue.GetType();
else
typeOfMyObject = newvalue.GetType();
PropertyInfo[] properties = typeOfMyObject.GetProperties();
var tasks = properties.ToList().Select(i => SetPropValuess(typeOfMyObject, i, Id, Action, RecordTimeStamp, oldervalue, newvalue, db, context));
await Task.WhenAll(tasks); // Compile error at this line
#endregion
}
catch
{
throw;
}
}
public async Task SetPropValuess(Type typeOfMyObject, PropertyInfo item, int Id, string Action, DateTime RecordTimeStamp, dynamic oldervalue, dynamic newvalue, VIBRANT db, VibrantAuditEntities context)
{
await Task.Run(() =>
{
string PropertyTypeNamespace = typeOfMyObject.GetProperty(item.Name).PropertyType.Namespace;
if (PropertyTypeNamespace != "System.Collections.Generic" && PropertyTypeNamespace != "ClassLibrary")
{
if (oldervalue != null && newvalue != null)
{
//Edit
bool chk = Comparer.Equals(oldervalue.GetType().GetProperty(item.Name).GetValue(oldervalue, null), newvalue.GetType().GetProperty(item.Name).GetValue(newvalue, null));
if (chk == false)
{
// make and add log record
AuditLog al = new AuditLog();
al.TableName = (typeOfMyObject.BaseType.Name == "Object" ? typeOfMyObject.Name : typeOfMyObject.BaseType.Name);
al.EventDateUTC = RecordTimeStamp;
al.OriginalValue = Convert.ToString(oldervalue.GetType().GetProperty(item.Name).GetValue(oldervalue, null));
al.NewValue = Convert.ToString(newvalue.GetType().GetProperty(item.Name).GetValue(newvalue, null));
al.ColumnName = item.Name;
context.AuditLogs.Add(al);
}
}
else if (oldervalue != null && newvalue == null)
{
//Delete
AuditLog al = new AuditLog();
al.TableName = (typeOfMyObject.BaseType.Name == "Object" ? typeOfMyObject.Name : typeOfMyObject.BaseType.Name);
al.EventDateUTC = RecordTimeStamp;
al.OriginalValue = Convert.ToString(typeOfMyObject.GetProperty(item.Name).GetValue(oldervalue, null));
al.NewValue = null;
al.ColumnName = item.Name;
al.EventType = Action;
context.AuditLogs.Add(al);
}
else if (oldervalue == null && newvalue != null)
{
//Create
AuditLog al = new AuditLog();
al.TableName = (typeOfMyObject.BaseType.Name == "Object" ? typeOfMyObject.Name : typeOfMyObject.BaseType.Name);
al.EventDateUTC = RecordTimeStamp;
al.OriginalValue = null;
al.NewValue = Convert.ToString(typeOfMyObject.GetProperty(item.Name).GetValue(newvalue, null));
al.ColumnName = item.Name;
context.AuditLogs.Add(al);
}
}
});
}
It's very strange, for some reason the fact that there is a parameter of type 'dynamic' affects the type of Tasks. If the dynamic parameters are removed then it works. As a workaround you can add a cast, but would be interesting to understand why this happens:.
var tasks = properties
.ToList()
.Select(i => SetPropValuess(typeOfMyObject, i, Id, Action, RecordTimeStamp, oldervalue, newvalue, db, context))
.Cast<Task>();

How to sanitize the value of string argument inside web api filter?

I have web api application and I want to sanitize data that comes from front-end applications using web api filters.
I have created the following filter:
public class StringFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
foreach (var actionArgument in actionContext.ActionArguments)
{
if (actionArgument.Value.GetType() == typeof(string))
{
var sanitizedString = actionArgument.Value.ToString().Trim();
sanitizedString = Regex.Replace(sanitizedString, #"\s+", " ");
actionContext.ActionArguments[actionArgument.Key] = sanitizedString;
}
else
{
var properties = actionArgument.Value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.CanRead && x.PropertyType == typeof(string) && x.GetGetMethod(true).IsPublic && x.GetSetMethod(true).IsPublic);
foreach (var propertyInfo in properties)
{
var sanitizedString = propertyInfo.GetValue(actionArgument.Value).ToString().Trim();
sanitizedString = Regex.Replace(sanitizedString, #"\s+", " ");
propertyInfo.SetValue(actionArgument.Value, sanitizedString);
}
}
}
}
}
The problem with this code is the code inside if statement where I want to sanitize the arguments passed as single string I got this error:
"ClassName": "System.InvalidOperationException",
"Message": "Collection was modified; enumeration operation may not execute.
But if my web api action takes a parameter as dto object which has string properties the code(which is inside the else statement) is working perfectly and strings are sanitized before starting executing the action.
So my question how to sanitize the passed argument in case it was string parameter?
You are trying to modify the same collection while enumerating it, which is not allowed. Call .ToList() so that you are enumerating a separate collection while modifying the original. ie foreach (var actionArgument in actionContext.ActionArguments.ToList()) {...
public override void OnActionExecuting(HttpActionContext actionContext) {
foreach (var actionArgument in actionContext.ActionArguments.ToList()) {
if (actionArgument.Value != null && actionArgument.Value is string) {
var sanitizedString = actionArgument.Value.ToString().Trim();
sanitizedString = Regex.Replace(sanitizedString, #"\s+", " ");
actionContext.ActionArguments[actionArgument.Key] = sanitizedString;
} else {
var properties = actionArgument.Value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(x => x.CanRead && x.PropertyType == typeof(string) && x.GetGetMethod(true).IsPublic && x.GetSetMethod(true).IsPublic);
foreach (var propertyInfo in properties) {
var sanitizedString = propertyInfo.GetValue(actionArgument.Value).ToString().Trim();
sanitizedString = Regex.Replace(sanitizedString, #"\s+", " ");
propertyInfo.SetValue(actionArgument.Value, sanitizedString);
}
}
}
}

Providing array of types as method parameter

I have a fairly simple method:
public static LinkItemCollection ToList<T>(this LinkItemCollection linkItemCollection)
{
var liCollection = linkItemCollection.ToList(true);
var newCollection = new LinkItemCollection();
foreach (var linkItem in liCollection)
{
var contentReference = linkItem.ToContentReference();
if (contentReference == null || contentReference == ContentReference.EmptyReference)
continue;
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
IContentData content = null;
var epiObject = contentLoader.TryGet(contentReference, out content);
if (content is T)
newCollection.Add(linkItem);
}
return newCollection;
}
This works fine - I can call the method and provide a type as T. However, what I want to be able to do is to be able to specify multiple types. I therefore, wrongly, assumed I could refactor the method to:
public static LinkItemCollection ToList(this LinkItemCollection linkItemCollection, Type[] types)
{
var liCollection = linkItemCollection.ToList(true);
var newCollection = new LinkItemCollection();
foreach (var linkItem in liCollection)
{
var contentReference = linkItem.ToContentReference();
if (contentReference == null || contentReference == ContentReference.EmptyReference)
continue;
foreach (var type in types)
{
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
IContentData content = null;
var epiObject = contentLoader.TryGet(contentReference, out content);
if (content is type)
newCollection.Add(linkItem);
}
}
return newCollection;
}
However, Visual Studio is showing it cannot resolve the symbol for type on the line if(content is type).
I know I'm doing something wrong, and I'm guessing I need to use Reflection here.
What you're looking for is:
type.IsAssignableFrom(content.GetType())
is is only used for checking against a type known at compile time, not at runtime.

How to inject call to System.Object.Equals with Mono.Cecil?

Using Mono.Cecil I want to rewrite the following property:
public string FirstName
{
get { return _FirstName; }
set
{
_FirstName = value;
}
}
to this:
public string FirstName
{
get { return _FirstName; }
set
{
if (System.Object.Equals(_FirstName, value))
{
return;
}
_FirstName = value;
}
}
This is just a snippet of what the rewrite will be but it is where I'm having a problem.
Using Reflector I can see the following code rewrites the property as required except the call to System.Object.Equals(). If expect the IL code to be:
call bool [mscorlib]System.Object::Equals(object, object)
but it is being written as:
call instance void RewriteSharp.Person::.ctor()
The code to write the call to System.Object.Equals is:
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));
The method used to init objectEqualsMethodReference is:
private static MethodReference GetSystemObjectEqualsMethodReference(
AssemblyDefinition assembly
)
{
var typeReference = assembly.MainModule.GetTypeReferences()
.Single(t => t.FullName == "System.Object");
var typeDefinition = typeReference.Resolve();
var methodDefinition = typeDefinition.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.Name == "Object"
&& m.Parameters[1].ParameterType.Name == "Object"
);
return methodDefinition;
}
It seems to me setMethodWriter.Create() or GetSystemObjectEqualsMethodReference() is incorrect and no amount of debugging has solved the problem.
The property being written and code to rewrite the property have the same framework target. 3.5 and 4.0 both fail.
I'm using the master branch https://github.com/jbevain/cecil to build Mono.Cecil.
Complete Code Listing
using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Linq;
namespace RewriteNotifyPropertyChanged
{
class Program
{
static void Main(string[] args)
{
var rewrite = "..\\RewriteSharp.dll";
var rewritten = "..\\RewritenSharp.dll";
var typeName = "Person";
var propertyName = "FirstName";
var assembly = AssemblyDefinition.ReadAssembly(rewrite);
var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName);
var propertyDefintion = typeDefinition.Properties
.Single(p => p.Name == propertyName);
var setMethodWriter = propertyDefintion.SetMethod.Body.GetILProcessor();
var backingFieldReference = GetBackingFieldReference(typeDefinition, propertyName);
var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);
var firstExistingInstruction = setMethodWriter.Body.Instructions[0];
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ldarg_0));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ldfld, backingFieldReference));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ldarg_1));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Brfalse_S, firstExistingInstruction));
setMethodWriter.InsertBefore(
firstExistingInstruction,
setMethodWriter.Create(OpCodes.Ret));
assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });
Console.WriteLine("Done.");
Console.ReadKey();
}
private static MethodReference GetSystemObjectEqualsMethodReference(
AssemblyDefinition assembly
)
{
var typeReference = assembly.MainModule.GetTypeReferences()
.Single(t => t.FullName == "System.Object");
var typeDefinition = typeReference.Resolve();
var methodDefinition = typeDefinition.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.Name == "Object"
&& m.Parameters[1].ParameterType.Name == "Object"
);
return methodDefinition;
}
private static FieldReference GetBackingFieldReference(
TypeDefinition typeDefinition,
string propertyName
)
{
var fieldName = "_" + propertyName;
var fieldReference = typeDefinition.Fields.Single(f => f.Name == fieldName);
return fieldReference;
}
}
}
Cecil, unlike System.Reflection, makes the distinction between a reference and a definition, and those are scoped per module. It means that you can't simply use a MethodDefinition from another module inside your own. You have to create a proper reference to it. This is a process called importing in the Cecil terminology.
Concretely, GetSystemObjectEqualsMethodReference returns a method defined in the corlib, you need to create a reference to it in your module :
Replacing:
var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);
by:
var objectEqualsMethodReference = assembly.MainModule.Import (GetSystemObjectEqualsMethodReference(assembly));
And fixing the IL should make it work.
Also, while I'm at it, the method:
private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly)
{
var typeReference = assembly.MainModule.GetTypeReferences()
.Single(t => t.FullName == "System.Object");
var typeDefinition = typeReference.Resolve();
var methodDefinition = typeDefinition.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.Name == "Object"
&& m.Parameters[1].ParameterType.Name == "Object"
);
return methodDefinition;
}
Would be better written as:
private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly)
{
var #object = assembly.MainModule.TypeSystem.Object.Resolve ();
return #object.Methods.Single(
m => m.Name == "Equals"
&& m.Parameters.Count == 2
&& m.Parameters[0].ParameterType.MetadataType == MetadataType.Object
&& m.Parameters[1].ParameterType.MetadataType == MetadataType.Object);
}
And
assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });
Doesn't make much sense if you don't pass new ReaderParameters { ReadSymbols = true } when reading the assembly.
You could take a look at KindOfMagic codeplex project.
It does almost the same, but little bit better - it does not call Object.Equals(), but equality operator defined on the target type.
http://kindofmagic.codeplex.com

Categories

Resources