C#. Set a member object value using reflection - c#

I need your help with the following code below. Basically I have a class called "Job" which has some public fields. I'm passing to my method "ApplyFilter" two parameters "job_in" and "job_filters". First parameter contains actual data, and the second one has instructions (if any). I need to iterate through "job_in" object, read it's data, apply any instructions by reading "job_filters", modify data (if needed) and return it in a new "job_out" object. Everything works fine till i need to store my data in "job_out" object:
public class Job
{
public string job_id = "";
public string description = "";
public string address = "";
public string details = "";
}
...
private Job ApplyFilters(Job job_in, Job job_filters)
{
Type type = typeof(Job);
Job job_out = new Job();
FieldInfo[] fields = type.GetFields();
// iterate through all fields of Job class
for (int i = 0; i < fields.Length; i++)
{
List<string> filterslist = new List<string>();
string filters = (string)fields[i].GetValue(job_filters);
// if job_filters contaisn magic word "$" for the field, then do something with a field, otherwise just copy it to job_out object
if (filters.Contains("$"))
{
filters = filters.Substring(filters.IndexOf("$") + 1, filters.Length - filters.IndexOf("$") - 1);
// MessageBox.Show(filters);
// do sothing useful...
}
else
{
// this is my current field value
var str_value = fields[i].GetValue(job_in);
// this is my current filed name
var field_name = fields[i].Name;
// I got stuck here :(
// I need to save (copy) data "str_value" from job_in.field_name to job_out.field_name
// HELP!!!
}
}
return job_out;
}
Please help. I've seen a few samples by using properties, but i'm pretty sure it is possible to do the same with fields as well. Thanks!

Try this
public static void MapAllFields(object source, object dst)
{
System.Reflection.FieldInfo[] ps = source.GetType().GetFields();
foreach (var item in ps)
{
var o = item.GetValue(source);
var p = dst.GetType().GetField(item.Name);
if (p != null)
{
Type t = Nullable.GetUnderlyingType(p.FieldType) ?? p.FieldType;
object safeValue = (o == null) ? null : Convert.ChangeType(o, t);
p.SetValue(dst, safeValue);
}
}
}

fields[i].SetValue(job_out, str_value);

Related

How can I change field value with Mono Cecil?

I have a C# program and it has a class:
public class Test
{
internal const string a = "some value";
private DateTime b = new DateTime();
}
How can I use Mono Cecil to change their initial value so that it looks like this:
public class Test
{
internal const string a = "test";
private DateTime b = DateTime.MaxValue;
}
Right now I only have the following skeleton code and I don't know how to modify the fields.
void Main()
{
var input = #"C:\my_projects\a.exe";
var asm = AssemblyDefinition.ReadAssembly(input);
foreach (ModuleDefinition module in asm.Modules)
{
foreach (TypeDefinition type in module.GetTypes())
{
foreach (var field in type.Fields)
{
if (field.Name == "a")
{
}
else if (field.Name == "b")
{
}
}
}
}
asm.Write(#"c:\my_projects\b.exe");
}
Disclaimer
Very brittle code ahead
For the constant it is a matter of setting fld.Constant property.
For instance/static fields the C# compiler will emit the initialization code in the constructor, so you'll need to find the instruction that is loading the value that will be stored in the field and replace it.
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace x
{
class Program
{
public const string ConstValue = "old";
public int instanceField = 1;
static void Main(string[] args)
{
var o = new Program();
if (o.instanceField == 1)
{
using var a = AssemblyDefinition.ReadAssembly(typeof(Program).Assembly.Location);
var t = a.MainModule.Types.Single(ct => ct.Name == "Program");
var constant = t.Fields.First(f => f.Name == "ConstValue");
constant.Constant = "new value";
var ctor = t.Methods.Single(m => m.IsConstructor);
System.Console.WriteLine(ctor);
var il = ctor.Body.GetILProcessor();
var inst = il.Body.Instructions.First();
while (inst != null)
{
System.Console.WriteLine($"Checking {inst}");
if (inst.OpCode == OpCodes.Stfld && ((FieldReference) inst.Operand).Name == "instanceField")
{
// notice that this is very fragile. For this specific
// case I'd say it is safe but depending on how you
// initialize the field the instruction that loads
// the value to be assigned to the field may be located
// several instructions prior to the actual assignment
// but you can keep track of the stack state and figure
// out which instruction is pushing the value that
// will end up being poped into the field.
il.Replace(inst.Previous, il.Create(OpCodes.Ldc_I4, 42));
System.Console.WriteLine("Found");
break;
}
inst = inst.Next;
}
var p = typeof(Program).Assembly.Location;
var newBinaryPath = Path.Combine(Path.GetDirectoryName(p), Path.GetFileNameWithoutExtension(p) + "2" +Path.GetExtension(p));
a.Write(newBinaryPath);
System.Console.WriteLine($"New binary writen to {newBinaryPath}");
}
System.Console.WriteLine(o.instanceField);
// Notice that no matter what you do, this will always print the
// old value; that happens because the C# compiler will emit the
// constant at the call site (instead of referencing the field
// at runtime)
System.Console.WriteLine(ConstValue);
}
}
}

How can I access multi-element List data stored in a public class?

My first question on SO:
I created this public class, so that I can store three elements in a list:
public class myMultiElementList
{
public string Role {get;set;}
public string Country {get;set;}
public int Commonality {get;set;}
}
In my main class, I then created a new list using this process:
var EmployeeRolesCountry = new List<myMultiElementList>();
var rc1 = new myMultiElementList();
rc1.Role = token.Trim();
rc1.Country = country.Trim();
rc1.Commonality = 1;
EmployeeRolesCountry.Add(rc1);
I've added data to EmployeeRolesCountry and have validated that has 472 lines. However, when I try to retrieve it as below, my ForEach loop only retrieves the final line added to the list, 472 times...
foreach (myMultiElementList tmpClass in EmployeeRolesCountry)
{
string d1Value = tmpClass.Role;
Console.WriteLine(d1Value);
string d2Value = tmpClass.Role;
Console.WriteLine(d2Value);
int d3Value = tmpClass.Commonality;
Console.WriteLine(d3Value);
}
This was the most promising of the potential solutions I found on here, so any pointers greatly appreciated.
EDIT: adding data to EmployeeRolesCountry
/*
Before this starts, data is taken in via a csvReader function and parsed
All of the process below is concerned with two fields in the csv
One is simply the Country. No processing necessary
The other is bio, and this itself needs to be parsed and cleansed several times to take roles out
To keep things making sense, I've taken much of the cleansing out
*/
private void File_upload_Click(object sender, EventArgs e)
{
int pos = 0;
var EmployeeRolesCountry = new List<myMultiElementList>();
var rc1 = new myMultiElementList();
int a = 0;
delimiter = ".";
string token;
foreach (var line in records.Take(100))
{
var fields = line.ToList();
string bio = fields[5];
string country = fields[4];
int role_count = Regex.Matches(bio, delimiter).Count;
a = bio.Length;
for (var i = 0; i < role_count; i++)
{
//here I take first role, by parsing on delimiter, then push back EmployeeRolesCountry with result
pos = bio.IndexOf('.');
if (pos != -1)
{
token = bio.Substring(0, pos);
string original_token = token;
rc1.Role = token.Trim();
rc1.Country = country.Trim();
rc1.Commonality = 1;
EmployeeRolesCountry.Add(rc1);
a = original_token.Length;
bio = bio.Remove(0, a + 1);
}
}
}
}
EDIT:
When grouped by multiple properties, this is how we iterate through the grouped items:
var employeesGroupedByRolwAndCountry = EmployeeRolesCountry.GroupBy(x => new { x.Role, x.Country });
employeesGroupedByRolwAndCountry.ToList().ForEach
(
(countryAndRole) =>
{
Console.WriteLine("Group {0}/{1}", countryAndRole.Key.Country, countryAndRole.Key.Role);
countryAndRole.ToList().ForEach
(
(multiElement) => Console.WriteLine(" : {0}", multiElement.Commonality)
);
}
);
__ ORIGINAL POST __
You are instantiating rc1 only once (outside the loop) and add the same instance to the list.
Please make sure that you do
var rc1 = new myMultiElementList();
inside the loop where you are adding the elements, and not outside.
All references are the same in your case:
var obj = new myObj();
for(i = 0; i < 5; i++)
{
obj.Prop1 = "Prop" + i;
list.Add(obj);
}
now the list has 5 elements, all pointing to the obj (the same instance, the same object in memory), and when you do
obj.Prop1 = "Prop" + 5
you update the same memory address, and all the pointers in the list points to the same instance so, you are not getting 472 copies of the LAST item, but getting the same instance 472 times.
The solution is simple. Create a new instance every time you add to your list:
for(i = 0; i < 5; i++)
{
var obj = new myObj();
obj.Prop1 = "Prop" + i;
list.Add(obj);
}
Hope this helps.

Setting the value to null using LINQ in list of list

I have a class
public class ReceiptDisplayInfo
{
public string ReceiptItemFor{get;set;}
public string ReceiptItemCategory{get;set;}
public string ReceiptItemReference{get;set;}
public string ReceiptRowCategory{get;set;}
public string ReceiptAmount{get;set;}
}
I have a list
List<List<ReceiptDisplayInfo>> dataSourceToBind ;
My requirement : For every List , if ReceiptRowCategory="Payment" , I have to set the value of ReceiptItemForm,ReceiptItemCategory to blank or null in dataSourceToBind .
I am doing using for loop but this is not the most appreciated approach.
Please assist me in doing using LINQ/Lambda Expression.
dataSourceToBind.ForEach(x =>
{
var innerList = x;
innerList.ForEach(y =>
{
if (y.ReceiptRowCategory == "Payment")
{
y.ReceiptItemFor = null;
y.ReceiptItemCategory = null;
}
});
});
I guess just 2 ForEach calls would suffice, no need to use LINQ here. However, since the transformation logic is quite complicated, I think you should extract it as a method:
private void SomeMethod(ReceiptDisplayInfo info) { // please name this appropriately
if (info.ReceiptRowCategory == "Payment") {
info.ReceiptItemForm = null;
info.ReceiptItemCategory = null;
}
}
And then,
dataSourceToBind.ForEach(x => x.ForEach(SomeMethod));
You can use below code to achieve this-
((from l in list
where l.ReceiptItemCategory == "payment"
select new ReceiptDisplayInfo()
{
ReceiptItemFor = null,
ReceiptItemCategory = null,
ReceiptItemReference = l.ReceiptItemReference,
ReceiptRowCategory = l.ReceiptRowCategory,
ReceiptAmount = l.ReceiptAmount
}).Union(from l in list
where l.ReceiptItemCategory != "payment"
select l)).ToList();

How to get all the values from an SQL Query C#

I have used String Builder to generate a RAW SQL QUERY in C#.
List<string> columns = new List<string>();
columns.Add("id");
columns.Add("Temp");
StringBuilder SqlStatement = new StringBuilder();
SqlStatement.Append("Select ");
for (int i = 0; i < columns.Count; i++)
{
if (i == columns.Count - 1)
{
SqlStatement.Append(columns[i]);
}
else
{
SqlStatement.Append(columns[i]);
SqlStatement.Append(",");
}
}
SqlStatement.Append(" FROM graph_update");
var ctx = new graphDBContext();
var result = ctx.Database.SqlQuery<graphDBContext>(SqlStatement.ToString()).ToList();
This translates into SELECT id,Temp FROM graph_update
And the result gives me
id = 1, temp = 20
id = 2 temp = 30
How do I get all these values????
I'm too use to:
foreach(var item in result)
{
item.id = id;
item.temp = temp;
}
But it won't let me.
EDIT:
Sorry but I'm not sure what you mean. Here is my debugger
Try to use foreach like this if theres no error return
foreach(var v in result)
{
String v0 = v[0].ToString();
String v1 = v[1].ToString();
}
Assuming you have EF > 6, then the ctx.Database.SqlQuery, according to the method documentation:
Creates a raw SQL query that will return elements of the given generic type.
The type can be any type that has properties that match the names of the columns returned from the query, or can be a simple primitive type. The type does not have to be an entity type. The results of this query are never tracked by the context even if the type of object returned is an entity type.
With that in mind you can do something like this:
public class GraphUpdateResult
{
public int Id {get; set;}
public decimal Temp {get; set;}
}
Then in your current method:
var result = ctx.Database.SqlQuery<GraphUpdateResult>SqlStatement.ToString()).ToList();
foreach (var graphResult in result)
{
Console.WriteLine(graphResult.Id);
Console.WriteLine(graphResult.Temp);
}
You can add more columns to the GraphUpdateResult class for EF to bind to, even if in some queries you don't specify them in the select statement.
I hope this helps.
foreach(var item in result)
{
var id = item.id;
var temp = item.temp;
}
in your code above, you are trying to assign the values to the item, instead of retrieving.
You can use a ORM-Mapper like
https://stormy.codeplex.com/SourceControl/latest#Stormy.cs
It is a very light Mapper and you can look how it works.
It maps the reader Data to the Object data:
public class CatMapper : ISelectable<Cat>
{
public Cat ApplySelect(IDataReader reader)
{
return new Cat()
{
Name = reader["name"].ToString(),
Weight = (float)reader["weight"]
};
}
}

Is there a way to check the data in advance of runtime if it's not the correct type?

I occasionally get data that is not completely clean, and during runtime I get error messages because the data doesn't match the expected type. For example, sometimes the data has a string where there should be an int, or an int where there should be a date.
Is there a way to scan the data first for bad data, so that I can fix it all at once instead of finding out during run-time and fixing it iteratively?
Here's my code which works:
class TestScore{
public string Name;
public int Age;
public DateTime Date;
public DateTime Time;
public double Score;
}
//read data
var Data = File.ReadLines(FilePath).Select(line => line.Split('\t')).ToArray();
//select data
var query = from x in Data
select new { Name = x[3], Age = x[1], Date = x[2], Time = x[5], Score = x[7] };
//create List and put data into List
List<TestScore> Results = new List<TestScore>();
for (int i = 0; i < query.Count; i++)
{
TestScore TS = new TestScore();
TS.Name = query[i].Name;
TS.Age = query[i].Age;
TS.Date = query[i].Date;
TS.Time = query[i].Time;
TS.Score = query[i].Score;
Results.Add(TS);
}
Is there a way to scan the data first for bad data, so that I can fix
it all at once instead of finding out during run-time and fixing it
iteratively?
Scanning is a runtime operation. However, it's fairly straightforward to implement a solution that gives you enough information to "fix it all at once".
The following code shows a pattern for validating the file in its entirety, and doesn't attempt to load any data unless it completely succeeds.
If it fails, a collection of all errors encountered is returned.
internal sealed class ParseStatus
{
internal bool IsSuccess;
internal IReadOnlyList<string> Messages;
}
private ParseStatus Load()
{
string filePath = "foo";
var data = File.ReadLines( filePath ).Select( line => line.Split( '\t' ) ).ToArray();
var results = from x in data
select new { Name = x[3], Age = x[1], Date = x[2], Time = x[5], Score = x[7] };
var errors = new List<string>();
int row = 0;
// first pass: look for errors by testing each value
foreach( var line in results )
{
row++;
int dummy;
if( !int.TryParse( line.Age, out dummy ) )
{
errors.Add( "Age couldn't be parsed as an int on line " + row );
}
// etc...use exception-free checks on each property
}
if( errors.Count > 0 )
{
// quit, and return errors list
return new ParseStatus { IsSuccess = false, Messages = errors };
}
// otherwise, it is safe to load all rows
// TODO: second pass: load the data
return new ParseStatus { IsSuccess = true };
}
For not finding out the errors during run-time, the best thing that I can think of would be to correct the data manually before your program runs ..
But as we are trying do things constructive, I think that using a static readonly field to indicate the data error would be helpful. The following is a simple example which doesn't take the failed items, you might want to modify it when you are going to do some advanced handling.
public partial class TestScore {
public static TestScore Parse(String plainText) {
var strings=plainText.Split('\t');
var result=new TestScore();
if(
strings.Length<5
||
!double.TryParse(strings[4], out result.Score)
||
!DateTime.TryParse(strings[3], out result.Time)
||
!DateTime.TryParse(strings[2], out result.Date)
||
!int.TryParse(strings[1], out result.Age)
)
return TestScore.Error;
result.Name=strings[0];
return result;
}
public String Name;
public int Age;
public DateTime Date;
public DateTime Time;
public double Score;
public static readonly TestScore Error=new TestScore();
}
public static partial class TestClass {
public static void TestMethod() {
var path=#"some tab splitted file";
var lines=File.ReadAllLines(path);
var format=""
+"Name: {0}; Age: {1}; "
+"Date: {2:yyyy:MM:dd}; Time {3:hh:mm}; "
+"Score: {4}";
var list=(
from line in lines
where String.Empty!=line
let result=TestScore.Parse(line)
where TestScore.Error!=result
select result).ToList();
foreach(var item in list) {
Console.WriteLine(
format,
item.Name, item.Age, item.Date, item.Time, item.Score
);
}
}
}

Categories