I am learning the reflections concepts in c#. I have a class like this
public class pdfClass
{
public List<AttributeProperties> TopA { get; set; }
public List<AttributeProperties> TopB { get; set; }
public List<AttributeProperties> TopC { get; set; }
}
In another class I would like to extract the values from the list. I have stupid ways to do it like
public void ExtractValue (pdfClass incomingpdfClass, string type)
{
switch (type)
{
case "TopA":
foreach (var listitem in incomingPdfClass.TopA)
{...}
breaks;
case "TopB":
foreach (var listitem in incomingPdfClass.TopB)
{...}
breaks;
...
}
}
The operations in the foreach loops are similar. How can I do this in a clear way by using reflections?
public void ExtractValue(pdfClass incomingpdfClass, string type)
{
PropertyInfo pinfo = typeof(pdfClass).GetProperty("Top" + type);
var yourList = pinfo.GetValue(incomingpdfClass);
foreach (var listitem in yourList)
{ ... }
}
This is how you should do this using reflection. However, you should note that my code differs from yours in the fact that you are writing code that isn't clear nor would it compile. AS
public class ExtractValue (pdfClass incomingpdfClass, string type)
is non valid C# syntax if that is supposed to be a function as per my example this will work for you
Or if this is supposed to happen in the Constructor for the class it should look as follows
public class ExtractValue
{
public ExtractValue(pdfClass incomingpdfClass, string type)
{
PropertyInfo pinfo = typeof(pdfClass).GetProperty("Top" + type);
var yourList = pinfo.GetValue(incomingpdfClass);
foreach (var listitem in yourList)
{ ... }
}
}
var property = this.GetType().GetProperty(type);
foreach (var item in (List<AttributeProperties>)property.GetValue(this, null))
{
}
If you have instance of pdfClass you do not need to use reflection for accessing lists.
I would suggest to decouple type from strategy itself by persisting such a dictionary:
IDictionary<string, Func<pdfClass, AttributeProperties, bool>> strategy;
Once add relations like
strategy.Add("TopA", (pdf, item) =>
{
return pdf.TopA.IndexOf(item) >= 0;
});
and use like
string itemType = "TopA";
if (strategy.ContainsKey(itemType) )
{
bool found = strategy[itemType](incommingPdfClass, listItem);
}
Related
I am trying to iterate over a list that I grab by doing solutionInformation.GetType().GetProperties().GetValue(). But even though the type is an IList, I get an exception when I try to iterate over the value.
namespace NS
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
public class Accelerator
{
public string AccName { get; set; }
}
public class DeviceInfo
{
public int Ram { get; set; }
public List<Accelerator> Accelerator { get; set; }
}
public class SolutionPods
{
public DeviceInfo DeviceInfo { get; set; }
}
public class Solution
{
public List<SolutionPods> SolutionPods { get; set; }
}
public class HelloWorld
{
public static void Main(string[] args)
{
var acc1 = new Accelerator { AccName = "accelerator Name" };
List<Accelerator> accList1 = new List<Accelerator>();
accList1.Add(acc1);
var devInfo1 = new DeviceInfo { Ram = 64, Accelerator = accList1 };
var solComponet = new SolutionPods { DeviceInfo = devInfo1 };
List<SolutionPods> SCList = new List<SolutionPods>();
SCList.Add(solComponet);
var solution = new Solution { SolutionPods = SCList };
foreach (var solutionComponet in solution.SolutionPods)
{
var solutionInformation = solutionComponet.DeviceInfo;
foreach (var solutionProperty in solutionInformation.GetType().GetProperties())
{
var solutionValue = solutionProperty.GetValue(solutionInformation);
if (solutionValue is IList)
{
Console.WriteLine("IList found");
// System.Collections.Generic.List`1[NS.Accelerator]
Console.WriteLine(solutionValue);
/*
/tmp/AFBHdRy49g.cs(64,43): error CS1579: foreach statement cannot operate on variables of type 'object' because 'object' does not contain a public instance definition for 'GetEnumerator'
*/
foreach (var s in solutionValue)
{
Console.WriteLine(s);
}
}
}
}
Console.WriteLine("Setup Complete");
}
}
}
Am I not actually grabbing a list? Or do I need to convert the object into a different form so that it has an GetEnumerator property?
In C#, once a variable is declared, it keeps the type it's declared as. In your case, the var solutionValue uses the inferred type of object because that's what Property.GetValue() returns.
The compiler does not implement type narrowing when it encounters a conditional like this:
if (solutionValue is IList)
However, you can use pattern matching to declare a new variable with the correct type:
if (solutionValue is IList solutions)
{
...
foreach (var s in solutions)
The compiler does a static type check that the value provided is (statically) an IEnumerable. An object is not. A (IList)solutionValue would be.
if (solutionValue is IList list) // joining a type test and a cast
{
foreach (var s in list) // here list is an IList
{
Console.WriteLine(s);
}
}
I want to set variables from an object using Reflection.
For simple object this works. (Properties)
But objects with class variables (Fields) doesn’t work. Here I get always an Exeption with "The object does not agree with the target type."
Has anyone here an idea how it could go?
class Program
{
static void Main(string[] args)
{
var genericDataSet = new GenericDataSet<DataObjekt>();
var returnObjekt = genericDataSet.KeepElementsData();
}
}
public class DataObjekt
{
public string Name { get; set; }
public ObjektData ModelTyp;
public DataObjekt() { ModelTyp = new ObjektData(); }
}
public class ObjektData
{
public string Typ { get; set; }
public string Data { get; set; }
}
public class GenericDataSet<T> where T : class, new()
{
public T KeepElementsData()
{
var item = new T();
//Propertys durchlaufen
foreach (var Property in item.GetType().GetProperties())
{
item.GetType().GetProperty(Property.Name).SetValue(item, "TestData"); //this works
}
//Fields durchlaufen
foreach (var Field in item.GetType().GetFields())
{
foreach (var FieldProperty in item.GetType().GetField(Field.Name).FieldType.GetProperties())
{
var data = item.GetType().GetField(Field.Name).FieldType.GetProperty(FieldProperty.Name);
data.SetValue(item, "TestData not work", null); // this doesent work
}
}
return item;
}
}
The reason it doesn't work is because you are setting the value on the wrong object:
data.SetValue(item, "TestData not work", null);
item doesn't have this property, its the field that has it.
You need to create an instance of that field (if its null), then fill its properties and then set it to the field.
The following will work for you:
public class GenericDataSet<T> where T : class, new()
{
public T KeepElementsData()
{
var item = new T();
//Propertys durchlaufen
foreach (var propertyInfo in item.GetType().GetProperties())
{
item.GetType().GetProperty(propertyInfo.Name).SetValue(item, "TestData"); //this works
}
//Fields durchlaufen
foreach (var fieldInfo in item.GetType().GetFields())
{
object fieldObject = Activator.CreateInstance(fieldInfo.FieldType);
// Or if it's already instantiated:
// object fieldObject = fieldInfo.GetValue(item);
foreach (var fieldProperty in fieldInfo.FieldType.GetProperties())
{
fieldProperty.SetValue(fieldObject, "TestData not work", null); // this doesent work
}
fieldInfo.SetValue(item, fieldObject);
}
return item;
}
}
As far as I can see this shouldn´t even work for properties, as you provide only string-data, while not all of your properties have string-type. Anyway in your fields-loop why do you have a nested loop at all? You´re looping the properties of every fields type, which is quite bpring I guess. So if your field has type string you iterate in the inner loop all the fields from String. You should be able to omit the inner loop and write the same what you´re doing for properties. Furthermore you can directly set the properties value for the current item.
var item = new T();
//Propertys durchlaufen
foreach (var property in item.GetType().GetProperties())
{
property.SetValue(item, "TestData"); //this works
}
//Fields durchlaufen
foreach (var field in item.GetType().GetFields())
{
field.SetValue(item, "TestData");
}
Try this:
public T KeepElementsData()
{
var item = new T();
//Propertys durchlaufen
foreach (var property in item.GetType().GetProperties())
{
property.SetValue(item, "TestData"); //this works
}
//Fields durchlaufen
foreach (var field in item.GetType().GetFields())
{
var value = field.GetValue(item);
var type = value.GetType();
foreach (var fieldProperty in type.GetProperties())
{
fieldProperty.SetValue(value, "TestData works");
}
}
return item;
}
You are going back and forth between the PropertyInfo->name of the PropertyInfo->PropertyInfo... plus you are mixing the item object with its fields...
I have a class as follows:
public class Feature
{
public string Name { get; set; }
public string DisplayName { get; set; }
public List<Feature> SubFeatures { get; set; } = new List<Feature>();
}
I then have a List<Feature> features = new List<Feature>; where I store all my features.
Now, I'd like to know if a particular Feature (by name) exists in my features variable.
However, it can exist at any level (SubFeature of the SubFeature of the SubFeature for example).
The closest I've gotten was this:
public bool FeatureExists(Feature feature, string name)
{
return feature.Name == name || feature.SubFeatures.Select(subFeature => FeatureExists(subFeature, name)).Any(result => result);
}
But it involves having to use a for on the caller of FeatureExists() to pass one top level feature at a time to it.
I'm sure there's an easier way to do this, how can I do this properly?
Define a recursive method like this:
public IEnumerable<Feature> FeatureAndSubFeatures(Feature feature)
{
yield return feature;
foreach (var subFeature in feature.SubFeatures)
{
foreach (var child in FeatureAndSubFeatures(subFeature))
{
yield return child;
}
}
}
Then use it:
FeatureAndSubFeatures(feature).Any(x => x.Name == name);
Another option is to put this method on Feature itself, called something like SelfAndSubFeaturesRecursive().
This approach - writing a method to recursively flatten the tree, rather than writing a specific method to search for a Feature with the given name - is flexible, as you can use it to search the tree for any node based on any criterion, or any subset of nodes, rather than being specialised for just finding nodes with a particular name.
You can also write it to take a collection of features to start with. Something like:
public IEnumerable<Feature> FeaturesAndSubFeatures(IEnumerable<Feature> features)
{
foreach (var feature in features)
{
yield return feature;
foreach (var child in FeaturesAndSubFeatures(feature.SubFeatures))
{
yield return child;
}
}
}
This is only useful if you're always starting with a collection of features, but saves a SelectMany in the event that you do.
I try to avoid recursion whenever possible. This is a version without:
public bool FeatureExists(Feature feature, string name)
{
var featureQueue = new Queue<Feature>();
featureQueue.Enqueue(feature);
while (featureQueue.Count > 0)
{
Feature current = featureQueue.Dequeue();
if (feature.Name == name)
return true;
foreach (Feature f in current.SubFeatures)
featureQueue.Enqueue(f);
}
return false;
}
If you find this less readable as commented you can use a generic extension method and use it whenever you need recursive checks, for example:
public static class Extensions
{
public static bool RecursiveCheck<T>(this T rootItem, Func<T, IEnumerable<T>> getChildrenFunc, Func<T, bool> predicate)
{
var queue = new Queue<T>();
queue.Enqueue(rootItem);
while (queue.Count > 0)
{
T current = queue.Dequeue();
if (predicate(current))
return true;
foreach (T child in getChildrenFunc(current))
queue.Enqueue(child);
}
return false;
}
}
Test-Feature:
Feature f1 = new Feature
{
Name = "1", SubFeatures = new List<Feature> { new Feature {Name="1.1", SubFeatures = new List<Feature> {new Feature {Name= "thename" } } }}
};
This simple one-liner remains:
bool containsName = f1.RecursiveCheck<Feature>(f => f.SubFeatures, f => f.Name == "thename");
You want to make a separate recursive method that does this for you.'
Try this:
public bool FeatureExists(Feature feature, string name) {
if(feature.Name == name) {
return true;
}
if(!feature.SubFeatures.isEmpty) {
foreach(Feature subFeature in feature.SubFeatures){
FeatureExists(subFeature, name)
}
}
return false;
}
the following should recurse through all the lists and populate "result" with all the possible features contained in the entire hierarchy
public class Feature
{
public string Name { get; set; }
public string DisplayName { get; set; }
public List<Feature> SubFeatures { get; set; } = new List<Feature>();
}
other class
List<feature> result = new List<feature>();
public void FindItems(Feature yourFeature)
{
result.add(yourFeature);
foreach(Feature feature in yourFeature)
{
if(feature.SubFeatures.count != 0)
{
foreach(Feature subfeature in feature)
{
FindItems(subfeature);
}
}
else
{
result.add(feature);
}
}
}
Well I'm not sure if this is what you are asking but if you want cleaner code you may write your function as an extension method and use LINQ instead of for loops to get cleaner code.
public static bool FeatureExists(this Feature feature, string name)
{
return feature.Name == name || feature.SubFeatures.Select(subFeature => FeatureExists(subFeature, name)).Any(result => result);
}
And then
List<Feature> mainFeatures = new List<Feature>();
mainFeatures.Any(obj => obj.FeatureExists("abc"));
If you want event shorter and cleaner code you might consider having a feature as a parent of all features like a mother feature and then call your recursive method over that.
But anyway consider making your method an extension method.
I'm cleaning up my code trying to short in some things
Now I've stumbled across:
ImageList.Add(test.Properties.Resources.test1);
ImageList.Add(test.Properties.Resources.test2);
ImageList.Add(test.Properties.Resources.test3);
ImageList.Add(test.Properties.Resources.test4);
ImageList.Add(test.Properties.Resources.test5);
(There are 15 of these)
Was wondering if this could be shortened with a for loop
Something like:
for(int i=1; i<=15; i++)
ImageList.Add(test.Properties.Resources.test +i);
Now ofcourse this won't work but I have no clue how to do this (if even possible)
You can iterate over resources via this code
using System.Collections;
using System.Globalization;
using System.Resources;
...
ResourceSet resourceSet = MyResourceClass.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
foreach (DictionaryEntry entry in resourceSet)
{
string resourceKey = entry.Key;
object resource = entry.Value;
}
You can use reflection, to get the values:
public class Something
{
public int Test1 { get; set; }
public int Test2 { get; set; }
public int Test3 { get; set; }
public int Test4 { get; set; }
}
var thing = new Something();
var imageProperties = typeof(Something)
.GetProperties()
.Where(p => p.Name.StartsWith("Test"));
var imagesToAdd = imageProperties
.Select(property => property.GetValue(thing))
.ToList();
You could define a property of type IEnumerable<Image> in the class of Resources object
public IEnumerable<Image> Images
{
get
{
yield return test1;
yield return test2;
yield return test3;
yield return test4;
yield return test5;
...
}
}
and then use it to fill ImageList
foreach(var image in test.Properties.Resources.Images)
{
ImageList.Add(image);
}
I just found out that there is a library for evaluating C# expression called Flee. Apparently you can use it to evaluate C# code so that you can loop over variable names, just like JavaScript, but the need for it most likely means a design flaw.
http://flee.codeplex.com/
I've managed to get something up and running today as small sandbox/POC project, but have seemed to bump my head on one issue...
Question:
Is there a way to get dapper to map to SQL column names with spaces in them.
I have something to this effect as my result set.
For example:
SELECT 001 AS [Col 1],
901 AS [Col 2],
00454345345345435349 AS [Col 3],
03453453453454353458 AS [Col 4]
FROM [Some Schema].[Some Table]
And my class would look like this
public class ClassA
{
public string Col1 { get; set; }
public string Col2 { get; set; }
///... etc
}
My implementation looks like this at the moment
public Tuple<IList<TClass>, IList<TClass2>> QueryMultiple<TClass, TClass2>(object parameters)
{
List<TClass> output1;
List<TClass2> output2;
using (var data = this.Connection.QueryMultiple(this.GlobalParameter.RpcProcedureName, parameters, CommandType.StoredProcedure))
{
output1 = data.Read<TClass>().ToList();
output2 = data.Read<TClass2>().ToList();
}
var result = new Tuple<IList<TClass>, IList<TClass2>>(output1, output2);
return result;
}
Note: The SQL cant be modified in any way.
Currently I'm going through the dapper code, and my only foreseeable solution is to add some code to "persuade" the column comparison, but not having much luck so far.
I've seen on StackOverflow that there are things like dapper extensions, but I'm hoping I can get this done without adding an extention, if not. I'll take whatever is quickest to implement.
There's a nuget package Dapper.FluentMap that allows you to add column name mappings (including spaces). It's similar to EntityFramework.
// Entity class.
public class Customer
{
public string Name { get; set; }
}
// Mapper class.
public class CustomerMapper : EntityMap<Customer>
{
public CustomerMapper()
{
Map(p => p.Name).ToColumn("Customer Name");
}
}
// Initialise like so -
FluentMapper.Initialize(a => a.AddMap(new CustomerMapper()));
see https://github.com/henkmollema/Dapper-FluentMap for more.
One option here would be to go via the dynamic / non-generic API, and then fetch the values out via the IDictionary<string,object> API per row, but that might be a bit tedious.
As an alternative, you can create a custom mapper, and tell dapper about it; for example:
SqlMapper.SetTypeMap(typeof(ClassA), new RemoveSpacesMap());
with:
class RemoveSpacesMap : Dapper.SqlMapper.ITypeMap
{
System.Reflection.ConstructorInfo SqlMapper.ITypeMap.FindConstructor(string[] names, Type[] types)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetMember(string columnName)
{
var prop = typeof(ClassA).GetProperty(columnName.Replace(" ", ""));
return prop == null ? null : new PropertyMemberMap(columnName, prop);
}
class PropertyMemberMap : Dapper.SqlMapper.IMemberMap
{
private string columnName;
private PropertyInfo property;
public PropertyMemberMap(string columnName, PropertyInfo property)
{
this.columnName = columnName;
this.property = property;
}
string SqlMapper.IMemberMap.ColumnName
{
get { throw new NotImplementedException(); }
}
System.Reflection.FieldInfo SqlMapper.IMemberMap.Field
{
get { return null; }
}
Type SqlMapper.IMemberMap.MemberType
{
get { return property.PropertyType; }
}
System.Reflection.ParameterInfo SqlMapper.IMemberMap.Parameter
{
get { return null; }
}
System.Reflection.PropertyInfo SqlMapper.IMemberMap.Property
{
get { return property; }
}
}
}
I had a similar problem when trying to get mapped results from a call to the system sp_spaceused procedure. Marc's code didn't quite work for me as it complained about not being able to find a default constructor. I also made my version generic so it could theoretically be re-used. This may not be the fastest performing piece of code, but it works for me and in our situation these calls are made infrequently.
class TitleCaseMap<T> : SqlMapper.ITypeMap where T: new()
{
ConstructorInfo SqlMapper.ITypeMap.FindConstructor(string[] names, Type[] types)
{
return typeof(T).GetConstructor(Type.EmptyTypes);
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
return null;
}
SqlMapper.IMemberMap SqlMapper.ITypeMap.GetMember(string columnName)
{
string reformattedColumnName = string.Empty;
foreach (string word in columnName.Replace("_", " ").Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
{
reformattedColumnName += char.ToUpper(word[0]) + word.Substring(1).ToLower();
}
var prop = typeof(T).GetProperty(reformattedColumnName);
return prop == null ? null : new PropertyMemberMap(prop);
}
class PropertyMemberMap : SqlMapper.IMemberMap
{
private readonly PropertyInfo _property;
public PropertyMemberMap(PropertyInfo property)
{
_property = property;
}
string SqlMapper.IMemberMap.ColumnName
{
get { throw new NotImplementedException(); }
}
FieldInfo SqlMapper.IMemberMap.Field
{
get { return null; }
}
Type SqlMapper.IMemberMap.MemberType
{
get { return _property.PropertyType; }
}
ParameterInfo SqlMapper.IMemberMap.Parameter
{
get { return null; }
}
PropertyInfo SqlMapper.IMemberMap.Property
{
get { return _property; }
}
}
}
I know this is an old question nevertheless i faced the same problem in my last project, so i just created an own mapper using attributes.
I defined an attribute class called ColumnNameAttribute.cs
using System;
namespace DapperHelper.Attributes
{
[System.AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]
sealed class ColumNameAttribute : Attribute
{
private string _columName;
public string ColumnName
{
get { return _columName; }
set { _columName = value; }
}
public ColumNameAttribute(string columnName)
{
_columName = columnName;
}
}
}
After defining the attribute, i implemeted a dynamic mapper that uses the Query method from Dapper but works as the Query<T>:
using Dapper;
using DapperHelper.Attributes;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Web.Routing;
namespace DapperHelper.Tools
{
public class DynamicMapper<T> :IDisposable where T : class, new()
{
private readonly Dictionary<string, PropertyInfo> _propertiesMap;
public DynamicMapper()
{
_propertiesMap = new Dictionary<string, PropertyInfo>();
PropertyInfo[] propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
if (propertyInfo.GetCustomAttribute(typeof(ColumNameAttribute)) is ColumNameAttribute columNameAttribute)
{
_propertiesMap.Add(columNameAttribute.ColumnName, propertyInfo);
}
else
{
_propertiesMap.Add(propertyInfo.Name, propertyInfo);
}
}
}
public List<T> QueryDynamic(IDbConnection dbConnection, string sqlQuery)
{
List<dynamic> results = dbConnection.Query(sqlQuery).ToList();
List<T> output = new List<T>();
foreach (dynamic dynObj in results)
{
output.Add(AssignPropertyValues(dynObj));
}
return output;
}
private T AssignPropertyValues(dynamic dynamicObject)
{
T output = new T();
RouteValueDictionary dynamicObjProps = new RouteValueDictionary(dynamicObject);
foreach (var propName in dynamicObjProps.Keys)
{
if (_propertiesMap.TryGetValue(propName, out PropertyInfo propertyMapped)
&& dynamicObjProps.TryGetValue(propName, out object value))
{
propertyMapped.SetValue(output, value);
}
}
return output;
}
public void Dispose()
{
_propertiesMap.Clear();
}
}
}
To use it, you have to refer to your Model class and define the attribute:
using DapperHelper.Attributes;
namespace Testing
{
public class Sample
{
public int SomeColumnData { get; set; }
[ColumnName("Your Column Name")]
public string SpecialColumn{ get; set; }
}
}
and then you can implement something like this:
DynamicMapper<Sample> mapper = new DynamicMapper<Sample>();
List<Sample> samples = mapper.QueryDynamic(connection, "SELECT * FROM Samples");
I hope it can help someone looking for an alternative.