C# Parameter count mismatch converting List to DataTable - c#

I am getting the exception mentioned in the title at the line of code
values[i] = Props[i].GetValue(item, null);
and I am not sure what is causing this. Any help would be appreciated.
public static DataTable ToDataTable<T>(List<T> items)
{
DataTable dataTable = new DataTable(typeof(T).Name);
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in Props)
{
var type = (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? Nullable.GetUnderlyingType(prop.PropertyType) : prop.PropertyType);
dataTable.Columns.Add(prop.Name, type);
}
foreach (T item in items)
{
var values = new object[Props.Length];
for (int i = 0; i < Props.Length; i++)
{
values[i] = Props[i].GetValue(item, null);
}
dataTable.Rows.Add(values);
}
return dataTable;
}

If this line is throwing the exception
values[i] = Props[i].GetValue(item, null);
...then it means you have a property that requires a parameter. In c#, the only type of property that takes a parameter is an indexer. My guess is you should just exclude the indexer from the loop.
See this question which tells you how to detect an indexer.
You can probably fix this by changing one line. Change this...
PropertyInfo[] Props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
...to this....
PropertyInfo[] Props = typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetIndexParameters == null))
.ToArray();

The issue was actually in the data that I passed into the method(a list of strings). It appeared that the GetValue() method was looking for an object. I created a class with a string property and changed my list type that was being passed in to the class and set the property in the class to the string that I was passing to the list in my foreach loop. Sorry if this doesn't make sense just trying to explain how I solved this, but the problem probably could have been solved a number of ways. Thanks, all.

Related

c# Object Comparison With Complex Objects

I have a generic object comparison method which I use to compare two models with the same structure.
public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
var variances = new List<Variance>();
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties.Where(t => t.IsMarkedWith<IncludeInComparisonAttribute>()))
{
var v = new Variance
{
PropertyName = property.Name,
ValA = property.GetValue(val1, null),
ValB = property.GetValue(val2, null)
};
if (v.ValA == null && v.ValB == null) { continue; }
if (v.ValA != null && !v.ValA.Equals(v.ValB))
{
variances.Add(v);
}
}
return variances;
}
The problem I have is that sometimes an object is passed to it that may contain a list of other objects within it. Because it only compares at the top level it just returns that the object array was changed. Ideally I would like it to go through the nested array and look at the changed values as well.
Ideally I think it should probably make a recursive call when it finds an object array. Any ideas how I might go about this?
Edit - with working examples
Here are some .net fiddle examples of how this is meant to work.
This is the first code example that doesn't search down through the nested objects and just reports that the collection has changed (as per the code above):
https://dotnetfiddle.net/Cng7GI
returns:
Property: NumberOfDesks has changed from '5' to '4'
Property: Students has changed from 'System.Collections.Generic.List1[Student]' to 'System.Collections.Generic.List1[Student]'
Now if I try and call the DetailedCompare if I find a nested array using:
if (v.ValA is ICollection)
{
Console.WriteLine("I found a nested list");
variances.AddRange(v.ValA.DetailedCompare(v.ValB));
}
else if(v.ValA != null && !v.ValA.Equals(v.ValB)){
variances.Add(v);
}
it doesn't look like the recursive call works
https://dotnetfiddle.net/Ns1tx5
as I just get:
I found a nested list
Property: NumberOfDesks has changed from '5' to '4'
If I add:
var list = v.ValA.DetailedCompare<T>(v.ValB);
inside the Collection check, I get an error that:
object does not contain a definition for 'DetailedCompare' ... Cannot convert instance argument type 'object' to T
really what I want from it is just a single array of all the property names and their value changes.
Property: NumberOfDesks has changed from '5' to '4'
Property: Id has changed from '1' to '4'
Property: FirstName has changed from 'Cheshire' to 'Door'
etc
Calling the method recursively is the issue here.
If we call a method DetailedCompare recursively passing as parameters two objects all its fine - as we can get their properties and compare them.
However when we call DetailedCompare recursively passing a two list of objects - we can not just get the properties of those lists - but we need to traverse and get the properties of those list and compare their value.
IMHO it would be better to separate the logic using a helper method - so when we find a nested list - we can tackle the logic as I have described above.
This is the Extension class I have written
public static class Extension
{
public static List<Variance> Variances { get; set; }
static Extension()
{
Variances = new List<Variance>();
}
public static List<Variance> DetailedCompare<T>(this T val1, T val2)
{
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var v = new Variance
{
PropertyName = property.Name,
ValA = property.GetValue(val1, null),
ValB = property.GetValue(val2, null)
};
if (v.ValA == null && v.ValB == null)
{
continue;
}
if (v.ValA is ICollection)
{
Console.WriteLine("I found a nested list");
DetailedCompareList(v.ValA,v.ValB);
}
else if (v.ValA != null && !v.ValA.Equals(v.ValB))
{
Variances.Add(v);
}
}
return Variances;
}
private static void DetailedCompareList<T>(T val1, T val2)
{
if (val1 is ICollection collection1 && val2 is ICollection collection2)
{
var coll1 = collection1.Cast<object>().ToList();
var coll2 = collection2.Cast<object>().ToList();
for (int j = 0; j < coll1.Count; j++)
{
Type type = coll1[j].GetType();
PropertyInfo[] propertiesOfCollection1 = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] propertiesOfCollection2 = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < propertiesOfCollection1.Length; i++)
{
var variance = new Variance
{
PropertyName = propertiesOfCollection1[i].Name,
ValA = propertiesOfCollection1[i].GetValue(coll1[j]),
ValB = propertiesOfCollection2[i].GetValue(coll2[j])
};
if (!variance.ValA.Equals(variance.ValB))
{
Variances.Add(variance);
}
}
}
}
}
}
With the following result:
Limitations
This approach is bounded by the definition of your objects - hence it can only work with 1 level of depth.

Activator.CreateInstance with string

I'm trying to populate a generic List< T > from another List< U > where the field names match, something like the untested pseudocode below. Where I'm having problems is when T is a string, for instance, which has no parameterless constructor. I've tried adding a string directly to the result object, but this gives me the obvious error -- that a string is not of Type T. Any ideas of how to solve this issue? Thanks for any pointers.
public static List<T> GetObjectList<T, U>(List<U> givenObjects)
{
var result = new List<T>();
//Get the two object types so we can compare them.
Type returnType = typeof(T);
PropertyInfo[] classFieldsOfReturnType = returnType.GetProperties(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public);
Type givenType = typeof(U);
PropertyInfo[] classFieldsOfGivenType = givenType.GetProperties(
BindingFlags.Instance |
BindingFlags.Static |
BindingFlags.NonPublic |
BindingFlags.Public);
//Go through each object to extract values
foreach (var givenObject in givenObjects)
{
foreach (var field in classFieldsOfReturnType)
{
//Find where names match
var givenTypeField = classFieldsOfGivenType.Where(w => w.Name == field.Name).FirstOrDefault();
if (givenTypeField != null)
{
//Set the value of the given object to the return object
var instance = Activator.CreateInstance<T>();
var value = field.GetValue(givenObject);
PropertyInfo pi = returnType.GetProperty(field.Name);
pi.SetValue(instance, value);
result.Add(instance);
}
}
}
return result;
}
If T is string and you have already created custom code to convert your givenObject to a string, you just need to do an intermediate cast to object to add it to a List<T>:
public static List<T> GetObjectList2<T, U>(List<U> givenObjects) where T : class
{
var result = new List<T>();
if (typeof(T) == typeof(string))
{
foreach (var givenObject in givenObjects)
{
var instance = givenObject.ToString(); // Your custom conversion to string.
result.Add((T)(object)instance);
}
}
else
{
// Proceed as before
}
return result;
}
Incidentally, you are adding an instance of T to result for every property of T that matches a property name in U and for every item in givenObjects. I.e. if givenObjects is a list of length 1 and T is a class with 10 matching properties, result could end up with 10 entries. This looks wrong. Also, you need to watch out for indexed properties.
As an alternative to this approach, consider using Automapper, or serializing your List<U> to JSON with Json.NET then deserializing as a List<T>.

How can i get custom attribute prop's value?

I use MVC and c# .. how can i get property's value?
Thank you
foreach (var prop in this.GetType().GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(CalcProgressAttribute))))
{
//i need prop 's value here
}
Try this, to make GetProperty work as you expect:
foreach (var prop in this.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(prop => Attribute.IsDefined(prop, typeof(CalcProgressAttribute))))
{
object value = prop.GetValue(this, null);
}
I don't know wether your foreach loop works, because i can not proof the following part of your code:
.Where(prop => Attribute.IsDefined(prop, typeof(CalcProgressAttribute)))). But without the Where the loop will go through all properties.

My deep copy isn't doing a real deep copy

I have a class which I'm trying to run a deep copy on. One of the members of this class is 'MeshContainers' which is an instance of MeshContainerCollection.
MeshContainerCollection<> inherits from my SceneObjectCollection<> class which inherits from List<>
What I noticed is that the source object has 1 item inside the meshcontainercollection while the cloned object has 0.
When stepping through the DeepCopy process I noticed that when I try to get the fields for MeshContainerCollection, it doesn't find any.
Now MeshContainerCollection doens't have any direct fields (only inherited fields) so I thought that was the problem.
But I use:
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
Which (afaik) should also return the private inherited members.
I have looked through the existing BindingFlags but haven't been able to figure out if there is another BindingFlag I should use to get the inherited private fields.
Could someone tell me how I can manage to do a REAL deep copy?
Deep Copy method I'm using:
private static object Process(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
Type elementType = Type.GetType(
type.FullName.Replace("[]", string.Empty));
var array = obj as Array;
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
{
copied.SetValue(Process(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = FormatterServices.GetUninitializedObject(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, Process(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
EDIT1: I prefer not to do this by serialization but by reflection.
As mentioned in GetFields documentation,
private fields on base classes are not returned.
Try this method instead:
public static IEnumerable<FieldInfo> GetAllFields(this Type type)
{
IEnumerable<FieldInfo> fields = type.GetFields(
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (type.BaseType == null)
return fields;
else
return GetAllFields(baseType).Concat(fields);
}
(you might want to rewrite it to avoid all the enumerables and concatenations, but you get the idea)
one of the ways that you could achieve this is by serializing the object and then deserializing it back.. write a function to do the same..
FYI, your class needs to be marked [Serializable] for this..
there are some libraries out there that do the same.. Copyable is one of them.. I would suggest not to try and reinvent the wheel rather build up on this library as there are too many edge conditions to handle when you deep copy.. may be you can submit patches to the author too..

C# myclass.[(col.ColumnName)] = row[(col.ColumnName)] says identifier expected?

Following code gives a red squigly under "emp.(col.ColummnName)". Error is "identifier expected"
foreach (DataColumn col in dt.Columns)
{
emp.(col.ColumnName) = row[(col.ColumnName)];
}
emp is a custom class with property names which correspond to column names in dataTable dt.
I suspect that I have to construct the expression differently so that I can refer to a property of class emp with results of a method call (col.ColumnName).
Any ideas will be appreciated.
==========================
Final Answer with working function code;
public void rowToObject(ref DataRow dr, ref object myObj)
{
foreach (DataColumn dc in dr.Table.Columns)
{
string colName = dc.ColumnName;
object colValue = dr[colName];
if (object.ReferenceEquals(colValue, DBNull.Value))
{
colValue = null;
}
PropertyInfo pi = myObj.GetType().GetProperty(colName);
if (pi != null && colValue != null)
{
Type propType = null;
Type nullableType = Nullable.GetUnderlyingType(pi.PropertyType);
if (nullableType != null)
{
propType = nullableType;
}
else
{
propType = pi.PropertyType;
}
if (object.ReferenceEquals(propType, colValue.GetType()))
{
pi.SetValue(myObj, colValue, null);
}
}
}
}
This syntax is not supported, the property name must be known at compile time. You can use reflection instead:
foreach (DataColumn col in dt.Columns)
{
PropertyInfo prop = emp.GetType().GetProperty(col.ColumnName);
prop.SetValue(emp, row[col.ColumnName], null);
}
This dynamic access of class properties is really beneficial, but it's not as easy to implement as you are hoping for.
You need to look into Reflection... with this library, you can dynamically make and fill your classes, but it takes some reading and trial/error.
Here is a couple links that discuss your situation; however, you may want to look up a Reflection 101 tuturial first.:
http://www.codeproject.com/KB/cs/fast_dynamic_properties.aspx
Can C# Attributes access the Target Class?
.
.
Please vote if helpful
I'm not aware that you can do it in way that you're trying to do it. What you're looking for is reflection and you can find an example how it works in this post:
How to loop through all the properties of a class?

Categories