Related
I'm an amateur and not English native speaker. I'm trying to do mapper for use it with Dapper's dynamic results, I need it for a personal project and I couldn't adapt self Dapper mapper either Automapper to my needs (probably due to my lack of knowledge), so I decided to make mine.
It works flawlessly when I add the mapper code with "Add -> Existing Item", but if I try to use it as a DLL it throws the "not accessible due to access level" exception, pointing this part of the code:
public T Map(IEnumerable<dynamic> dapperResult, bool cleanResult = false)
{
var parser = new PrePostFixesParser(this);
T mapped = this.NewObject(dapperResult.First()); <------//EXCEPTION HERE
if (_OnlyConstructor.Contains(this.TType)) return mapped;
//... Other things
The method NewObject:
private T NewObject(dynamic dyn)
{
Type t = typeof(T);
bool IsInterfaceNotIEnumerable = t.IsInterface
&& !typeof(IDictionary).IsAssignableFrom(t)
&& !(typeof(IEnumerable).IsAssignableFrom(t) && !typeof(string).IsAssignableFrom(t));
if (IsInterfaceNotIEnumerable)
throw new CustomException_DapperMapper(
#"DapperMapper.NewObject: Exception trying to instantiate an interface that isn't an IEnumerable. This is a BUG.");
T newObj;
//if there are a constructor configurated
if (_Constructors.ContainsKey(this.TType))
{
//object[] constructorParams = GetConstructorParams();
try
{
Func<dynamic, T> constDelegate = (Func<dynamic, T>)_Constructors[this.TType];
newObj = constDelegate(dyn);
}
catch (Exception err)
{
throw new CustomException_DapperMapper(
$#"DapperMapper.NewObject: Exception using constructor to create object of type {TType.Name} with delegate {_Constructors[TType]}.", err);
}
}
//if there are no constructor configurated, use parameterless constructor
else newObj = Activator.CreateInstance<T>();
return newObj;
}
Both methods are part of the same class called DapperMapper. I've read the page always want all the possible code in the same question, so I'm posting the entire class here, but it's a large one, so I'm posting too the link to Github where I have all the solution... just in case:
public class DapperMapper<T> : DMStatic_Mapper, iDapperMapper
{
public DapperMapper(MapperStore store)
{
this.TType = typeof(T);
this.MappersStore = store;
this.MappersStore.StoreMapper(this.TType, this);
}
#region properties
public MapperStore MappersStore { get; private set; }
public Dictionary<MemberInfo, MemberTypeInfo> mtInfos { get { return _mtInfos[this.TType]; } }
public Type TType { get; private set; }
public IEnumerable<string> NamesList { get { return _NamesList[this.TType]; } }
public Tuple<string[], bool> Prefixes { get { return _Prefixes.ContainsKey(this.TType) ? _Prefixes[this.TType] : null; } }
public Tuple<string[], bool> Postfixes { get { return _Postfixes.ContainsKey(this.TType) ? _Postfixes[this.TType] : null; } }
#endregion
#region helpers
private T NewObject(dynamic dyn)
{
Type t = typeof(T);
bool IsInterfaceNotIEnumerable = t.IsInterface
&& !typeof(IDictionary).IsAssignableFrom(t)
&& !(typeof(IEnumerable).IsAssignableFrom(t) && !typeof(string).IsAssignableFrom(t));
if (IsInterfaceNotIEnumerable)
throw new CustomException_DapperMapper(
#"DapperMapper.NewObject: Exception trying to instantiate an interface that isn't an IEnumerable. This is a BUG.");
T newObj;
//if there are a constructor configurated
if (_Constructors.ContainsKey(this.TType))
{
//object[] constructorParams = GetConstructorParams();
try
{
Func<dynamic, T> constDelegate = (Func<dynamic, T>)_Constructors[this.TType];
newObj = constDelegate(dyn);
}
catch (Exception err)
{
throw new CustomException_DapperMapper(
$#"DapperMapper.NewObject: Exception using constructor to create object of type {TType.Name}
with delegate {_Constructors[TType]}.", err);
}
}
//if there are no constructor configurated, use parameterless constructor
else newObj = Activator.CreateInstance<T>();
return newObj;
}
#endregion
#region public methods
/// <summary>
/// Remove duplicated results due to JOINs.
/// </summary>
/// <param name="origDapperResult"></param>
/// <param name="cleanResult"></param>
/// <returns></returns>
public IEnumerable<dynamic> GetDistinctDapperResult(IEnumerable<dynamic> origDapperResult, bool cleanResult)
{
PrePostFixesParser parser = new PrePostFixesParser(this);
IEnumerable<string> names = this.NamesList;
List<dynamic> result = new List<dynamic>();
foreach(dynamic dyn in origDapperResult)
{
IDictionary<string, object> dict =
(!cleanResult ? parser.GetTypeMembersWithoutPrePostFixes(dyn, names) : dyn)
as IDictionary<string, object>;
bool distinct = true;
foreach(dynamic resultDyn in result)
{
IDictionary<string, object> resDict = resultDyn as IDictionary<string, object>;
if(dict.Keys.SequenceEqual(resDict.Keys) && dict.Values.SequenceEqual(resDict.Values))
{
distinct = false;
break;
}
}
if (distinct) result.Add(dyn);
}
return result;
}
/// <summary>
/// Check if the dynamic object have all the members needed to map a new T object, except those setted as IEnumerable,
/// which should be provided in others dynamic.
/// </summary>
/// <param name="dyn"></param>
/// <returns></returns>
public bool CheckIfDynamicHasAllTypeMembersByName(dynamic dyn)
{
IDictionary<string, object> membersDict = dyn as IDictionary<string, object>;
IEnumerable<string> dynList = membersDict.Select(kvp => kvp.Key);
PrePostFixesParser parser = new PrePostFixesParser(this);
IEnumerable<string> list = parser.GetCleanNamesList(this.NamesList);
return !dynList.Except(list).Any() && !list.Except(dynList).Any();
}
/// <summary>
/// Check if the dynamic object have all the members needed to map a new T object, except those setted as IEnumerable,
/// which should be provided in others dynamic.
/// </summary>
/// <param name="membersDict"></param>
/// <returns></returns>
public bool CheckIfDynamicHasAllTypeMembersByName(IDictionary<string, object> membersDict)
{
IEnumerable<string> dynList = membersDict.Select(kvp => kvp.Key);
PrePostFixesParser parser = new PrePostFixesParser(this);
return dynList.SequenceEqual(parser.GetCleanNamesList(this.NamesList));
}
/// <summary>
/// Generic Map.
/// </summary>
/// <param name="dapperResult"></param>
/// <returns></returns>
public T Map(IEnumerable<dynamic> dapperResult, bool cleanResult = false)
{
var parser = new PrePostFixesParser(this);
T mapped = this.NewObject(dapperResult.First());
if (_OnlyConstructor.Contains(this.TType)) return mapped;
//TODO: divide el siguiente foreach en dos con dos nuevos diccionarios estáticos, uno para pInfos y otro para fInfos,
//aunque se repita código: hacer métodos para cada parte del código del tipo:
//private T PreMapCreator(KeyValuePair<PropertyInfo, MemberTypeInfo> kvp, IEnumerable<dynamic> dapperResult, bool cleanResult = false)
//private T PreMapIEnumerable(KeyValuePair<PropertyInfo, MemberTypeInfo> kvp, IEnumerable<dynamic> dapperResult, bool cleanResult = false)
//...
//Loop through all members
foreach (KeyValuePair<MemberInfo, MemberTypeInfo> kvp in mtInfos)
{
if (kvp.Value == MemberTypeInfo.Ignore)
continue;
//Member have a creator
else if ((kvp.Value & MemberTypeInfo.Creator) == MemberTypeInfo.Creator)
{
//MemberDelegate mDel = (MemberDelegate)_MembersCreators[this.TType][kvp.Key.Name];
Func<dynamic, object> mDel = (Func<dynamic, object>)_MembersCreators[this.TType][kvp.Key.Name];
if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, mDel(dapperResult));
else ((FieldInfo)kvp.Key).SetValue(mapped, mDel(dapperResult));
}
//Member is IDictionary or IEnumerable
else if ((kvp.Value & MemberTypeInfo.IEnumerable) == MemberTypeInfo.IEnumerable)
{
Type t = GetMemberType(kvp.Key);
//if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface) t = ResolveInterface(kvp.Key, dapperResult);
//else t = GetMemberType(kvp.Key);
/*
{
//Type of property or field
if (kvp.Key.MemberType == MemberTypes.Property) t = ((PropertyInfo)kvp.Key).PropertyType;
else t = ((FieldInfo)kvp.Key).FieldType;
}*/
bool isAnInterface = (kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface;
bool isNested = (kvp.Value & MemberTypeInfo.Nested) == MemberTypeInfo.Nested;
//If member is a dictionary
if (typeof(IDictionary).IsAssignableFrom(t))
{
//Create a dummy dictionary with the dapper's dynamic result which should be equal to the final one
DictionaryMapper dictMapper = new DictionaryMapper(dapperResult, kvp.Key.Name, isNested, isAnInterface, cleanResult, t, this);
try
{
if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, dictMapper.DummyDictionary);
else ((FieldInfo)kvp.Key).SetValue(mapped, dictMapper.DummyDictionary);
}
catch (Exception err)
{
throw new CustomException_DapperMapper(
$#"DapperMapper.Map: Couldn't map IDictionary member {kvp.Key.Name} with value contained by dynamic object.
Incorrect type of value?: {kvp.Value.ToString()}",
err);
}
}
//Rest of enumerables
else
{
IEnumerable<dynamic> iEnumDapperResult;
//Select current member's values from dynamic
if (isNested && !cleanResult)
{
//Type mType = t; // GetMemberType(kvp.Key);//IEnumerable<T>
Type genericType = t.GenericTypeArguments[0];//mType.GenericTypeArguments[0];//T
if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface)
{
bool genericIsInterfaceNotIEnumerable =
genericType.IsInterface &&
!typeof(IDictionary).IsAssignableFrom(genericType) &&
!(typeof(IEnumerable).IsAssignableFrom(genericType) && !typeof(string).IsAssignableFrom(genericType));
if (genericIsInterfaceNotIEnumerable) genericType = ResolveInterface(genericType, dapperResult);
}
iDapperMapper nestedMapper = MappersStore.GetMapper(genericType);
var nestedParser = new PrePostFixesParser(nestedMapper);
iEnumDapperResult = dapperResult
.Select(dyn => nestedParser.GetTypeMembersWithoutPrePostFixes(dyn, nestedMapper.NamesList));
}
else if (!cleanResult) iEnumDapperResult = dapperResult.Select(dyn => parser.RemovePrePostFixesFromDictionary(dyn));
else iEnumDapperResult = dapperResult;
//Create dummy IEnumerable
EnumerableMapper enumMapper = new EnumerableMapper(iEnumDapperResult, kvp.Key.Name, isNested, t, this.TType); ;
var dummy = Activator.CreateInstance(t, enumMapper.DummyEnumerable);
try
{
if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, dummy);
else ((FieldInfo)kvp.Key).SetValue(mapped, dummy);
}
catch (Exception err)
{
throw new CustomException_DapperMapper(
$#"DapperMapper.Map: Couldn't map IEnumerable member {kvp.Key.Name} with value contained by dynamic object.
Incorrect type of value?: {kvp.Value.ToString()}",
err);
}
}
}//End IDictionary/IEnumerable
//If Built-in
else if ((kvp.Value & MemberTypeInfo.BuiltIn) == MemberTypeInfo.BuiltIn)
{
string name = parser.RemoveFieldsUnderscore(kvp.Key.Name);
IDictionary<string, object> dapperDict;
if (!cleanResult)
dapperDict = parser.GetTypeMembersWithoutPrePostFixes(dapperResult.First(), NamesList) as IDictionary<string, object>;
else
dapperDict = dapperResult.First() as IDictionary<string, object>;
if (!dapperDict.ContainsKey(name))
throw new CustomException_DapperMapper(
$#"DapperMapper.Map: There's no member in dynamic dapper result with name {kvp.Key.Name}. Cannot Map object.");
try
{
if (kvp.Key.MemberType == MemberTypes.Property) ((PropertyInfo)kvp.Key).SetValue(mapped, dapperDict[name]);
else ((FieldInfo)kvp.Key).SetValue(mapped, dapperDict[name]);
}
catch (Exception err)
{
throw new CustomException_DapperMapper(
$#"DapperMapper.Map: Couldn't map BuiltIn-type member {kvp.Key.Name} with value contained by dynamic object.
Incorrect type of value?: {kvp.Value.ToString()}",
err);
}
}
//if nested
else if ((kvp.Value & MemberTypeInfo.Nested) == MemberTypeInfo.Nested)
{
Type mType = GetMemberType(kvp.Key);
if ((kvp.Value & MemberTypeInfo.Interface) == MemberTypeInfo.Interface)
mType = ResolveInterface(mType, dapperResult);
//access generic Map method through nongeneric interface method
iDapperMapper nestedMapper = MappersStore.GetMapper(mType);
if (nestedMapper == null)
throw new CustomException_DapperMapper(
$#"DapperMapper.Map: No Mapper found at store for property {kvp.Key.Name} of type {mType.ToString()}.
If you want to map a nested property you have to create a mapper for that property type.");
if (kvp.Key.MemberType == MemberTypes.Property)
((PropertyInfo)kvp.Key).SetValue(mapped, nestedMapper.NoGenericMap(dapperResult, cleanResult));
else ((FieldInfo)kvp.Key).SetValue(mapped, nestedMapper.NoGenericMap(dapperResult, cleanResult));
}
}
return mapped;
}
/// <summary>
/// Generic map to IEnumerables. Result HAVE to be ordered by the splitOn column.
/// </summary>
/// <typeparam name="R"></typeparam>
/// <param name="dapperResult"></param>
/// <param name="splitOn"></param>
/// <param name="cleanResult"></param>
/// <returns></returns>
public R Map<R>(IEnumerable<dynamic> dapperResult, string splitOn = "Id", bool cleanResult = false)
where R : IEnumerable<T>
{
R result;
Type r = typeof(R);
List<dynamic> singleObjectDynamic = new List<dynamic>();
object splitObject = (dapperResult.First() as IDictionary<string, object>)[splitOn];
if (typeof(IList).IsAssignableFrom(r))
{
result = (R)Activator.CreateInstance(typeof(List<>).MakeGenericType(this.TType));
foreach (dynamic dyn in dapperResult)
{
IDictionary<string, object> dict = dyn as IDictionary<string, object>;
if (!dict.ContainsKey(splitOn))
throw new CustomException_DapperMapper(
$#"DapperMapper.Map(IEnumerable): Dapper result doesn't have a member with name equals to splitOn parameter.
SplitOn = {splitOn}");
if (!object.Equals(splitObject, dict[splitOn]) || dapperResult.Last() == dyn)
{
((IList)result).Add(Map(singleObjectDynamic));
singleObjectDynamic.Clear();
splitObject = dict[splitOn];
}
else
singleObjectDynamic.Add(dyn);
}
}
else
{
//http://stackoverflow.com/questions/18251587/assign-any-ienumerable-to-object-property
var addMethod = r.GetMethod("Add", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
// Property doesn't support Adding
if (addMethod == null)
throw new CustomException_DapperMapper(
$#"DapperMapper.Map(IEnumerable): Method Add doesn't exist in enumerable type to map.
enumType: {r.Name}.");
if (r.IsGenericTypeDefinition) result = (R)Activator.CreateInstance(r.MakeGenericType(this.TType));
else result = (R)Activator.CreateInstance(r);
foreach (dynamic dyn in dapperResult)
{
IDictionary<string, object> dict = dyn as IDictionary<string, object>;
if (!dict.ContainsKey(splitOn))
throw new CustomException_DapperMapper(
$#"DapperMapper.Map(IEnumerable): Dapper result doesn't have a member with name equals to splitOn parameter.
SplitOn = {splitOn}");
if (!object.Equals(splitObject, dict[splitOn]) || dapperResult.Last() == dyn)
{
addMethod.Invoke(result, new object[] { NoGenericMap(dyn) });
singleObjectDynamic.Clear();
splitObject = dict[splitOn];
}
else
singleObjectDynamic.Add(dyn);
}
}
return result;
}
/// <summary>
/// Non-generic Map.
/// </summary>
/// <param name="dapperResult"></param>
/// <returns></returns>
public object NoGenericMap(dynamic dapperResult, bool cleanResult = false)
{
IEnumerable<dynamic> ienum = new List<dynamic>() { dapperResult } as IEnumerable<dynamic>;
return this.Map(ienum, cleanResult);
}
/// <summary>
/// Non-generic Map.
/// </summary>
/// <param name="dapperResult"></param>
/// <param name="cleanResult"></param>
/// <returns></returns>
public object NoGenericMap(IEnumerable<dynamic> dapperResult, bool cleanResult = false)
{
return this.Map(dapperResult, cleanResult);
}
#endregion
}
QUESTION
So, what's happening?
I'm astonished by this one. Am I terribly wrong or class's methods should be able to see same class private methods?
For the DLL I just began it with a new Class Library project in visual studio 2015 community, and when done build it with the typical release configuration, then I went to a console project I'm using to testing it (I just made a simple database there, to have direct Dapper's dynamic results objects to test it) and added reference to the DLL. Could it be that I build the DLL wrong? Do I have to do something more in the testing project apart from referencing the DLL and add the corresponding using namespace?
I've tried searching for private methods, DLLs, private methods in DLLs, private methods in the public class in DLLs, but it seems I'm not able to find anything that solves it. Apart from checking the class and method have the proper access keywords (c# default access is private if no other is specified, right?), and clean, build, rebuild again both solutions, I don't see anymore.
I'm afraid I'm just forgetting something stupid, but it works if I copy-paste the code (or add it with "Add->Existing Item" as I said before") instead of using the DLL, therefore the code is correct, right?
EDIT:
Due to comment of GSerg I'm adding the way I call DapperMapper in the testing project after adding the reference to the DLL:
MapperStore store = new MapperStore();
DapperMapper<Persons> mapper = (DapperMapper<Persons>)store.GetMapper(typeof(Persons));
Persons p = mapper.Map(result);
EDIT2:
As can be seen in the comments, as suggested by GSerg I created a new console project for testing, add the reference to the DLL, copy-paste ONLY the test (mock classes, database), hit F5 and got the same result. I double checked that I builded the DLL and the test project at release mode, and use windows explorer to confirm that there were no DLL's .cs files at the testing project.
In a Dapper ORM application, I want to assign one object to another, or all data members at once. Like this:
public class TableA
{
public int UserId { get; set; }
public string ClientId { get; set; }
// ... more fields ...
public bool Query()
{
bool Ok = false;
try{
// Method A
TableA Rec = QueryResultRecords.First();
MyCopyRec(Rec, this); // ugly
// Method B
this = QueryResultRecords.First(); // avoids CopyRec, does not work
Ok = true;
}
catch(Exception e){
Ok = false;
}
return Ok;
}
}
With Method A you can assign the object from .First() directly to a new object of class TableA, and need a custom method MyCopyRec to get the data in the data members of the same class.
However, with Method B you cannot assign the same object directly to this.
Or is there another way to do this?
You cannot assign a object to "this" if "this" is a reference type, e.g. a class. "this" is a pointer to the current class instance.
This would only work if this is a value type, e.g. a struct.
You can only assign values to properties of "this" (which is what probably happens in the (CopyRec method), like:
var result = QueryResultRecords.First();
this.UserId = result.UserId;
/// <summary>
/// Extension for 'Object' that copies the properties to a destination object.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
public static void CopyProperties(this object source, object destination)
{
// If any this null throw an exception
if (source == null || destination == null)
throw new ArgumentException("Source or/and Destination Objects are null");
// Getting the Types of the objects
Type typeDest = destination.GetType();
Type typeSrc = source.GetType();
// Iterate the Properties of the source instance and
// populate them from their desination counterparts
PropertyInfo[] srcProps = typeSrc.GetProperties();
foreach (PropertyInfo srcProp in srcProps)
{
if (!srcProp.CanRead)
{
continue;
}
PropertyInfo targetProperty = typeDest.GetProperty(srcProp.Name);
if (targetProperty == null)
{
continue;
}
if (!targetProperty.CanWrite)
{
continue;
}
if ((targetProperty.GetSetMethod().Attributes & MethodAttributes.Static) != 0)
{
continue;
}
if (!targetProperty.PropertyType.IsAssignableFrom(srcProp.PropertyType))
{
continue;
}
// Passed all tests, lets set the value
targetProperty.SetValue(destination, srcProp.GetValue(source, null), null);
}
}
Here's that method I talked about in the comments above.
Also refer to this link: Apply properties values from one object to another of the same type automatically?
I am trying to get a static field info into metro app and I don't find a way to do that.
I have tried:
- type.GetRuntimeField
- typeInfo.GetDeclaredField in a loop to delve into every parent types
/// <summary>
/// Gets the field info from the specified name
/// </summary>
/// <param name="type">The source type</param>
/// <param name="fieldName">The name of the field</param>
/// <returns>The field info if found, null otherwise</returns>
public static FieldInfo GetField(this Type type, string fieldName)
{
var currentType = type;
FieldInfo result = null;
while (result == null && currentType != null)
{
var typeInfo = currentType.GetTypeInfo();
result = typeInfo.GetDeclaredField(fieldName);
currentType = typeInfo.BaseType;
}
return result;
}
... am I missing something or is there anyway to get a static field on a type using reflection in metro app?....
edit:
Well, I am so sorry for those who have waste time on this question, Dependency properties defined in the framework are actualy not readonly static fields, they are static properties... As I usualy declare my dps as field, I didn't consider the fact that form example FrameworkElement.Width could be a property...
So here is the code I used to get fields and property info:
public static class TypeExtensions
{
/// <summary>
/// Gets the field info from the specified name
/// </summary>
/// <param name="type">The source type</param>
/// <param name="fieldName">The name of the field</param>
/// <returns>The field info if found, null otherwise</returns>
public static FieldInfo GetField(this Type type, string fieldName)
{
var currentType = type;
FieldInfo result = null;
while (result == null && currentType != null)
{
var typeInfo = currentType.GetTypeInfo();
result = typeInfo.GetDeclaredField(fieldName);
currentType = typeInfo.BaseType;
}
return result;
}
/// <summary>
/// Gets the property info from the specified name
/// </summary>
/// <param name="type">The source type</param>
/// <param name="propertyName">The name of the property</param>
/// <returns>The field info if found, null otherwise</returns>
public static PropertyInfo GetProperty(this Type type, string propertyName)
{
var currentType = type;
PropertyInfo result = null;
while (result == null && currentType != null)
{
var typeInfo = currentType.GetTypeInfo();
result = typeInfo.GetDeclaredProperty(propertyName);
currentType = typeInfo.BaseType;
}
return result;
}
}
public static class DependencyObjectExtensions
{
public static DependencyProperty GetDependencyProperty(this DependencyObject dependencyObject, string propertyName)
{
var dependencyPropertyName = propertyName + "Property";
var type = dependencyObject.GetType();
var fieldInfo = type.GetField(dependencyPropertyName);
if (fieldInfo == null)
{
var propertyInfo = type.GetProperty(dependencyPropertyName);
if (propertyInfo != null)
{
return propertyInfo.GetValue(dependencyObject) as DependencyProperty;
}
}
else
{
var value = fieldInfo.GetValue(dependencyObject);
return value as DependencyProperty;
}
return null;
}
}
Thanks a lot
Regards,
Charles
I'm writing a class able to get and set values from an object by using a string pattern, by means of reflection. The class works well, even on complex patterns, but I got un expected behaviour that I don't know how to solve/workaround.
Essentially, when the class is accessing to a field or property that is a value type, everything works, but it operates on a copy of the value type. Indeed, when I was to set a value using a string pattern, the real value type is not being updated.
The class mantains an object reference and a MemberInfo instance (those objects are got by analysing the access pattern on a root object); in this way I can get or set the member specified by MemberInfo starting from the object instance.
private static object GetObjectMemberValue(object obj, MemberInfo memberInfo, object[] memberArgs)
{
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
// Get the value
switch (memberInfo.MemberType) {
case MemberTypes.Field: {
FieldInfo fieldInfo = (FieldInfo)memberInfo;
if (fieldInfo.FieldType.IsValueType) {
TypedReference typedReference = __makeref(obj);
return (fieldInfo.GetValueDirect(typedReference));
} else
return (fieldInfo.GetValue(obj));
}
case MemberTypes.Property:
return (((PropertyInfo)memberInfo).GetValue(obj, memberArgs));
case MemberTypes.Method:
return (((MethodInfo)memberInfo).Invoke(obj, memberArgs));
default:
throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
}
}
private static void SetObjectMemberValue(object obj, MemberInfo memberInfo, params object[] memberArgs)
{
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
// Set the value
switch (memberInfo.MemberType) {
case MemberTypes.Field: {
FieldInfo fieldInfo = (FieldInfo)memberInfo;
if (fieldInfo.FieldType.IsValueType) {
TypedReference typedReference = __makeref(obj);
fieldInfo.SetValueDirect(typedReference, memberArgs[0]);
} else
fieldInfo.SetValue(obj, memberArgs[0]);
} break;
case MemberTypes.Property:
((PropertyInfo)memberInfo).SetValue(obj, memberArgs[0], null);
break;
case MemberTypes.Method:
((MethodInfo)memberInfo).Invoke(obj, memberArgs);
break;
default:
throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
}
}
When the obj parameter is a struct value, it happens the error: I get/set from the boxed value.
How can I workaround this? I've already checked this question, but without success (you can see the code on field management): the boxing happens all the same since I assign the field value into a object variable.
The make things more clear, here is the complete code of class in question:
// Copyright (C) 2012 Luca Piccioni
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
namespace Derm
{
/// <summary>
/// Class able to read and write a generic object.
/// </summary>
/// <remarks>
/// <para>
/// This class supports the access to one of the following:
/// - A specific object field
/// - A specific object property (even indexed)
/// - A specific object method (even with arguments)
/// </para>
/// </remarks>
public class ObjectAccessor
{
#region Constructors
/// <summary>
/// Construct an ObjectAccessor that access to an object's field or property.
/// </summary>
/// <param name="container">
/// A <see cref="System.Object"/> that specify a generic member.
/// </param>
/// <param name="memberPattern">
/// A <see cref="System.String"/> that specify the pattern of the member of <paramref name="container"/>.
/// </param>
public ObjectAccessor(object container, string memberPattern)
{
if (container == null)
throw new ArgumentNullException("container");
if (memberPattern == null)
throw new ArgumentNullException("memberPattern");
// Store member pattern
mMemberPattern = memberPattern;
Dictionary<int, string> stringMap = new Dictionary<int,string>();
object containerMember = container;
int stringMapIndex = 0;
// Remove (temporarly) strings enclosed by double-quotes
memberPattern = Regex.Replace(memberPattern, "\"[^\\\"]*\"", delegate(Match match) {
stringMap[stringMapIndex++] = match.Value;
return (String.Format("{{{0}}}", stringMapIndex - 1));
});
string[] members = Regex.Split(memberPattern, #"\.");
// Restore strings enclosed by double-quotes
for (int i = 0; i < members.Length; i++ ) {
members[i] = Regex.Replace(members[i], #"{(?<StringOrder>\d+)}", delegate(Match match) {
return (stringMap[Int32.Parse(match.Groups["StringOrder"].Value)]);
});
}
if (members.Length > 1) {
StringBuilder containerMemberPattern = new StringBuilder(memberPattern.Length);
for (int i = 0; i < members.Length - 1; i++ ) {
MemberInfo memberInfo;
object[] memberArgs;
// Pattern for exception message
containerMemberPattern.AppendFormat(".{0}", members[i]);
// Access to the (intermediate) member
GetObjectMember(containerMember, members[i], out memberInfo, out memberArgs);
// Get member value
containerMember = GetObjectMemberValue(containerMember, memberInfo, memberArgs);
if (containerMember == null)
throw new InvalidOperationException(String.Format("the field {0} is null", containerMemberPattern.ToString()));
if ((memberInfo.MemberType != MemberTypes.Field) && (containerMember.GetType().IsValueType == true))
throw new NotSupportedException("invalid pattern becuase operating on strcuture copy");
}
}
// Store container object
mContainer = container;
// Store object
mObject = containerMember;
// Get member
GetObjectMember(mObject, members[members.Length - 1], out mMember, out mMemberArgs);
}
#endregion
#region Object Access
/// <summary>
/// Get the type of the accessed member.
/// </summary>
public Type MemberType
{
get
{
switch (mMember.MemberType) {
case MemberTypes.Field:
return (((FieldInfo)mMember).FieldType);
case MemberTypes.Property:
return (((PropertyInfo)mMember).PropertyType);
default:
throw new NotSupportedException(mMember.MemberType + " is not supported");
}
}
}
/// <summary>
/// Get the value of the object member.
/// </summary>
/// <returns></returns>
public object Get()
{
switch (mMember.MemberType) {
case MemberTypes.Field: {
FieldInfo fieldInfo = (FieldInfo)mMember;
if (fieldInfo.FieldType.IsValueType) {
object referenceObject = mObject;
TypedReference typedReference = __makeref(referenceObject);
return (fieldInfo.GetValueDirect(typedReference));
} else
return (fieldInfo.GetValue(mObject));
}
case MemberTypes.Property:
if (((PropertyInfo)mMember).CanRead == false)
throw new InvalidOperationException("write-only property");
return (((PropertyInfo)mMember).GetValue(mObject, null));
default:
throw new NotSupportedException(mMember.MemberType + " is not supported");
}
}
/// <summary>
/// Set the value of the object member.
/// </summary>
/// <param name="value"></param>
public void Set(object value)
{
switch (mMember.MemberType) {
case MemberTypes.Field: {
FieldInfo fieldInfo = (FieldInfo)mMember;
if (fieldInfo.FieldType.IsValueType) {
object referenceObject = mObject;
TypedReference typedReference = __makeref(referenceObject);
fieldInfo.SetValueDirect(typedReference, value);
} else
fieldInfo.SetValue(mObject, value);
} break;
case MemberTypes.Property:
if (((PropertyInfo)mMember).CanWrite == false)
throw new InvalidOperationException("read-only property");
((PropertyInfo)mMember).SetValue(mObject, value, null);
break;
default:
throw new NotSupportedException(mMember.MemberType + " is not supported");
}
}
/// <summary>
/// The object used for getting the object implementing <see cref="mMember"/>. In simple cases
/// it equals <see cref="mObject"/>.
/// </summary>
private readonly object mContainer;
/// <summary>
/// The object that specify the field/property pointed by <see cref="mMember"/>.
/// </summary>
private readonly object mObject;
/// <summary>
/// The pattern used for getting/setting the member of <see cref="mObject"/>.
/// </summary>
private readonly string mMemberPattern;
/// <summary>
/// Field, property or method member of <see cref="mObject"/>.
/// </summary>
private readonly MemberInfo mMember;
/// <summary>
/// Arguments list specified at member invocation.
/// </summary>
private readonly object[] mMemberArgs;
#endregion
#region Object Member Access
/// <summary>
/// Access to an object member.
/// </summary>
/// <param name="obj">
/// A <see cref="System.Object"/> which type defines the underlying member.
/// </param>
/// <param name="memberPattern">
/// A <see cref="System.String"/> that specify how the member is identified. For methods and indexed properties, the arguments
/// list is specified also.
/// </param>
/// <param name="memberInfo">
/// A <see cref="System.Reflection.MemberInfo"/> that represent the member.
/// </param>
/// <param name="memberArgs">
/// An array of <see cref="System.Object"/> that represent the argument list required for calling a method or an indexed
/// property.
/// </param>
private static void GetObjectMember(object obj, string memberPattern, out MemberInfo memberInfo, out object[] memberArgs)
{
if (obj == null)
throw new ArgumentNullException("obj");
if (memberPattern == null)
throw new ArgumentNullException("memberPattern");
Type objType = obj.GetType();
Match methodMatch;
if ((methodMatch = sCollectionRegex.Match(memberPattern)).Success || (methodMatch = sMethodRegex.Match(memberPattern)).Success) {
MemberInfo[] members = objType.GetMember(methodMatch.Groups["MethodName"].Value);
ParameterInfo[] methodArgsInfo;
int bestMemberIndex = 0;
if ((members == null) || (members.Length == 0))
throw new InvalidOperationException(String.Format("no property/method {0}", memberPattern));
string[] args = Regex.Split(methodMatch.Groups["MethodArgs"].Value, " *, *");
if (members.Length != 1) {
Type[] argsType = new Type[args.Length];
bestMemberIndex = -1;
// Try to guess method arguments type to identify the best overloaded match
for (int i = 0; i < args.Length; i++)
argsType[i] = GuessMethodArgumentType(args[i]);
if (Array.TrueForAll<Type>(argsType, delegate(Type type) { return (type != null); })) {
for (int i = 0; i < members.Length; i++) {
if (members[i].MemberType == MemberTypes.Property) {
methodArgsInfo = ((PropertyInfo)members[i]).GetIndexParameters();
Debug.Assert((methodArgsInfo != null) && (methodArgsInfo.Length > 0));
} else if (members[i].MemberType == MemberTypes.Method) {
methodArgsInfo = ((MethodInfo)members[i]).GetParameters();
} else
throw new NotSupportedException("neither a method or property");
// Parameters count mismatch?
if (methodArgsInfo.Length != args.Length)
continue;
// Parameter type incompatibility?
bool compatibleArgs = true;
for (int j = 0; j < args.Length; j++) {
if (argsType[j] != methodArgsInfo[j].ParameterType) {
compatibleArgs = false;
break;
}
}
if (compatibleArgs == false)
continue;
bestMemberIndex = i;
break;
}
}
if (bestMemberIndex == -1)
throw new InvalidOperationException(String.Format("method or property {0} has an ambiguous definition", memberPattern));
}
// Method or indexed property
memberInfo = members[bestMemberIndex];
// Parse method arguments
if (memberInfo.MemberType == MemberTypes.Property) {
methodArgsInfo = ((PropertyInfo)memberInfo).GetIndexParameters();
Debug.Assert((methodArgsInfo != null) && (methodArgsInfo.Length > 0));
} else if (memberInfo.MemberType == MemberTypes.Method) {
methodArgsInfo = ((MethodInfo)memberInfo).GetParameters();
} else
throw new NotSupportedException("neither a method or property");
if (args.Length != methodArgsInfo.Length)
throw new InvalidOperationException("argument count mismatch");
memberArgs = new object[args.Length];
for (int i = 0; i < args.Length; i++) {
Type argType = methodArgsInfo[i].ParameterType;
if (argType == typeof(String)) {
memberArgs[i] = args[i].Substring(1, args[i].Length - 2);
} else if (argType == typeof(Int32)) {
memberArgs[i] = Int32.Parse(args[i]);
} else if (argType == typeof(UInt32)) {
memberArgs[i] = UInt32.Parse(args[i]);
} else if (argType == typeof(Single)) {
memberArgs[i] = Single.Parse(args[i]);
} else if (argType == typeof(Double)) {
memberArgs[i] = Double.Parse(args[i]);
} else if (argType == typeof(Int16)) {
memberArgs[i] = Int16.Parse(args[i]);
} else if (argType == typeof(UInt16)) {
memberArgs[i] = UInt16.Parse(args[i]);
} else if (argType == typeof(Char)) {
memberArgs[i] = Char.Parse(args[i]);
} else if (argType == typeof(Byte)) {
memberArgs[i] = Byte.Parse(args[i]);
} else
throw new InvalidOperationException(String.Format("argument of type {0} is not supported", argType.Name));
}
} else {
MemberInfo[] members = objType.GetMember(memberPattern);
if ((members == null) || (members.Length == 0))
throw new InvalidOperationException(String.Format("no property/field {0}", memberPattern));
if (members.Length > 1) {
members = Array.FindAll<MemberInfo>(members, delegate(MemberInfo member) {
return (member.MemberType == MemberTypes.Property || member.MemberType == MemberTypes.Field);
});
}
if (members.Length != 1)
throw new InvalidOperationException(String.Format("field of property {0} has an ambiguous definition", memberPattern));
// Property of field
memberInfo = members[0];
// Not an indexed property
memberArgs = null;
}
}
/// <summary>
/// Access to the object member.
/// </summary>
/// <param name="obj">
/// A <see cref="System.Object"/> which type defines the underlying member.
/// </param>
/// <param name="memberInfo">
/// A <see cref="System.Reflection.MemberInfo"/> that represent the member.
/// </param>
/// <param name="memberArgs">
/// An array of <see cref="System.Object"/> that represent the argument list required for calling a method or an indexed
/// property.
/// </param>
/// <returns></returns>
private static object GetObjectMemberValue(object obj, MemberInfo memberInfo, object[] memberArgs)
{
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
// Get the value
switch (memberInfo.MemberType) {
case MemberTypes.Field: {
FieldInfo fieldInfo = (FieldInfo)memberInfo;
if (fieldInfo.FieldType.IsValueType) {
TypedReference typedReference = __makeref(obj);
return (fieldInfo.GetValueDirect(typedReference));
} else
return (fieldInfo.GetValue(obj));
}
case MemberTypes.Property:
return (((PropertyInfo)memberInfo).GetValue(obj, memberArgs));
case MemberTypes.Method:
return (((MethodInfo)memberInfo).Invoke(obj, memberArgs));
default:
throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
}
}
private static void SetObjectMemberValue(object obj, MemberInfo memberInfo, params object[] memberArgs)
{
if (memberInfo == null)
throw new ArgumentNullException("memberInfo");
// Set the value
switch (memberInfo.MemberType) {
case MemberTypes.Field: {
FieldInfo fieldInfo = (FieldInfo)memberInfo;
if (fieldInfo.FieldType.IsValueType) {
TypedReference typedReference = __makeref(obj);
fieldInfo.SetValueDirect(typedReference, memberArgs[0]);
} else
fieldInfo.SetValue(obj, memberArgs[0]);
} break;
case MemberTypes.Property:
((PropertyInfo)memberInfo).SetValue(obj, memberArgs[0], null);
break;
case MemberTypes.Method:
((MethodInfo)memberInfo).Invoke(obj, memberArgs);
break;
default:
throw new InvalidOperationException(String.Format("the type of the member {0}.{1} is not supported", obj.GetType().Name, memberInfo.Name));
}
}
private static Type GuessMethodArgumentType(string methodArg)
{
if (String.IsNullOrEmpty(methodArg))
throw new ArgumentNullException("methodArg");
if (sMethodArgString.IsMatch(methodArg))
return (typeof(String));
return (null);
}
/// <summary>
/// Regular expression used for matching method calls.
/// </summary>
private static readonly Regex sMethodRegex = new Regex(#"^(?<MethodName>\w+) *\( *(?<MethodArgs>.*) *\)$");
/// <summary>
/// Regular expression used for matching method string arguments.
/// </summary>
private static readonly Regex sMethodArgString = new Regex(#"\"".*\""");
/// <summary>
/// Regular expression used for matching collection indexer calls.
/// </summary>
private static readonly Regex sCollectionRegex = new Regex(#"^(?<MethodName>\w+) *\[ *(?<MethodArgs>.*) *\]$");
#endregion
}
}
__makeref is an undocumented keyword. I have never seen it used before so don't know exactly what it is doing. However, you can accomplish what I assume __makeref is trying to do just by casting the value type to object before modifying.
Jon Skeet explains the specifics in this answer
https://stackoverflow.com/a/6280540/141172
On a side note, undocumented things have a way of changing over time. I would not rely on them for production code.
If you declare your obj parameter as a ref variable, then maybe you can assign back to it after you changed your struct. Is this a mutable/changable struct?
I'm not sure why it's relevant to see if the field type is a value type. I thought we were discussing the case where obj.GetType().IsValueType?
Addition:
I've thought a bit about it, and I no longer think it will work to make the parameter ref if you have boxing. It shouldn't even be necessary.
I think your problem is only with the Set method? It looks like you didn't include your use of SetObjectMemberValue. But I suspect you want to use it like this:
var myMutableStruct = XXX;
SetObjectMemberValue(myMutableStruct, instanceFieldInfo, 42);
// use myMutableStruct with new field value
This can never work with a struct, because it's a boxed copy you pass to the method. No matter what the method does, it has only access to that copy. Instead you could say:
var myMutableStruct = XXX;
object boxToKeep = myMutableStruct;
SetObjectMemberValue(myMutableStruct, instanceFieldInfo, 42);
myMutableStruct = (MyMutableStruct)boxToKeep;
// use myMutableStruct with new field value
If you don't like this, try making the method generic in the type of obj. The signature could then be SetObjectMemberValue<TObj>(TObj obj, MemberInfo memberInfo, params object[] memberArgs). With a generic type, no boxing occurs, but you will probably need to use the magic of __makeref or make the parameter ref (so ref TObj obj) with reassignment inside the method body. See the Stack Overflow thread you link yourself in your question.
I have a list of integer values (List) and would like to generate a string of comma delimited values. That is all items in the list output to a single comma delimted list.
My thoughts...
1. pass the list to a method.
2. Use stringbuilder to iterate the list and append commas
3. Test the last character and if it's a comma, delete it.
What are your thoughts? Is this the best way?
How would my code change if I wanted to handle not only integers (my current plan) but strings, longs, doubles, bools, etc, etc. in the future? I guess make it accept a list of any type.
It's amazing what the Framework already does for us.
List<int> myValues;
string csv = String.Join(",", myValues.Select(x => x.ToString()).ToArray());
For the general case:
IEnumerable<T> myList;
string csv = String.Join(",", myList.Select(x => x.ToString()).ToArray());
As you can see, it's effectively no different. Beware that you might need to actually wrap x.ToString() in quotes (i.e., "\"" + x.ToString() + "\"") in case x.ToString() contains commas.
For an interesting read on a slight variant of this: see Comma Quibbling on Eric Lippert's blog.
Note: This was written before .NET 4.0 was officially released. Now we can just say
IEnumerable<T> sequence;
string csv = String.Join(",", sequence);
using the overload String.Join<T>(string, IEnumerable<T>). This method will automatically project each element x to x.ToString().
in 3.5, i was still able to do this. Its much more simpler and doesnt need lambda.
String.Join(",", myList.ToArray<string>());
I explain it in-depth in this post. I'll just paste the code here with brief descriptions.
Here's the method that creates the header row. It uses the property names as column names.
private static void CreateHeader<T>(List<T> list, StreamWriter sw)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length - 1; i++)
{
sw.Write(properties[i].Name + ",");
}
var lastProp = properties[properties.Length - 1].Name;
sw.Write(lastProp + sw.NewLine);
}
This method creates all the value rows
private static void CreateRows<T>(List<T> list, StreamWriter sw)
{
foreach (var item in list)
{
PropertyInfo[] properties = typeof(T).GetProperties();
for (int i = 0; i < properties.Length - 1; i++)
{
var prop = properties[i];
sw.Write(prop.GetValue(item) + ",");
}
var lastProp = properties[properties.Length - 1];
sw.Write(lastProp.GetValue(item) + sw.NewLine);
}
}
And here's the method that brings them together and creates the actual file.
public static void CreateCSV<T>(List<T> list, string filePath)
{
using (StreamWriter sw = new StreamWriter(filePath))
{
CreateHeader(list, sw);
CreateRows(list, sw);
}
}
You can create an extension method that you can call on any IEnumerable:
public static string JoinStrings<T>(
this IEnumerable<T> values, string separator)
{
var stringValues = values.Select(item =>
(item == null ? string.Empty : item.ToString()));
return string.Join(separator, stringValues.ToArray());
}
Then you can just call the method on the original list:
string commaSeparated = myList.JoinStrings(", ");
If any body wants to convert list of custom class objects instead of list of string then override the ToString method of your class with csv row representation of your class.
Public Class MyClass{
public int Id{get;set;}
public String PropertyA{get;set;}
public override string ToString()
{
return this.Id+ "," + this.PropertyA;
}
}
Then following code can be used to convert this class list to CSV with header column
string csvHeaderRow = String.Join(",", typeof(MyClass).GetProperties(BindingFlags.Public | BindingFlags.Instance).Select(x => x.Name).ToArray<string>()) + Environment.NewLine;
string csv= csvHeaderRow + String.Join(Environment.NewLine, MyClass.Select(x => x.ToString()).ToArray());
You can use String.Join.
String.Join(
",",
Array.ConvertAll(
list.ToArray(),
element => element.ToString()
)
);
As the code in the link given by #Frank Create a CSV File from a .NET Generic List there was a little issue of ending every line with a , I modified the code to get rid of it.Hope it helps someone.
/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
if (list == null || list.Count == 0) return;
//get type from 0th member
Type t = list[0].GetType();
string newLine = Environment.NewLine;
if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));
if (!File.Exists(csvCompletePath)) File.Create(csvCompletePath);
using (var sw = new StreamWriter(csvCompletePath))
{
//make a new instance of the class name we figured out to get its props
object o = Activator.CreateInstance(t);
//gets all properties
PropertyInfo[] props = o.GetType().GetProperties();
//foreach of the properties in class above, write out properties
//this is the header row
sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);
//this acts as datarow
foreach (T item in list)
{
//this acts as datacolumn
var row = string.Join(",", props.Select(d => item.GetType()
.GetProperty(d.Name)
.GetValue(item, null)
.ToString())
.ToArray());
sw.Write(row + newLine);
}
}
}
I like a nice simple extension method
public static string ToCsv(this List<string> itemList)
{
return string.Join(",", itemList);
}
Then you can just call the method on the original list:
string CsvString = myList.ToCsv();
Cleaner and easier to read than some of the other suggestions.
For whatever reason, #AliUmair reverted the edit to his answer that fixes his code that doesn't run as is, so here is the working version that doesn't have the file access error and properly handles null object property values:
/// <summary>
/// Creates the CSV from a generic list.
/// </summary>;
/// <typeparam name="T"></typeparam>;
/// <param name="list">The list.</param>;
/// <param name="csvNameWithExt">Name of CSV (w/ path) w/ file ext.</param>;
public static void CreateCSVFromGenericList<T>(List<T> list, string csvCompletePath)
{
if (list == null || list.Count == 0) return;
//get type from 0th member
Type t = list[0].GetType();
string newLine = Environment.NewLine;
if (!Directory.Exists(Path.GetDirectoryName(csvCompletePath))) Directory.CreateDirectory(Path.GetDirectoryName(csvCompletePath));
using (var sw = new StreamWriter(csvCompletePath))
{
//make a new instance of the class name we figured out to get its props
object o = Activator.CreateInstance(t);
//gets all properties
PropertyInfo[] props = o.GetType().GetProperties();
//foreach of the properties in class above, write out properties
//this is the header row
sw.Write(string.Join(",", props.Select(d => d.Name).ToArray()) + newLine);
//this acts as datarow
foreach (T item in list)
{
//this acts as datacolumn
var row = string.Join(",", props.Select(d => $"\"{item.GetType().GetProperty(d.Name).GetValue(item, null)?.ToString()}\"")
.ToArray());
sw.Write(row + newLine);
}
}
}
Any solution work only if List a list(of string)
If you have a generic list of your own Objects like list(of car) where car has n properties, you must loop the PropertiesInfo of each car object.
Look at: http://www.csharptocsharp.com/generate-csv-from-generic-list
CsvHelper library is very popular in the Nuget.You worth it,man!
https://github.com/JoshClose/CsvHelper/wiki/Basics
Using CsvHelper is really easy. It's default settings are setup for the most common scenarios.
Here is a little setup data.
Actors.csv:
Id,FirstName,LastName
1,Arnold,Schwarzenegger
2,Matt,Damon
3,Christian,Bale
Actor.cs (custom class object that represents an actor):
public class Actor
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Reading the CSV file using CsvReader:
var csv = new CsvReader( new StreamReader( "Actors.csv" ) );
var actorsList = csv.GetRecords();
Writing to a CSV file.
using (var csv = new CsvWriter( new StreamWriter( "Actors.csv" ) ))
{
csv.WriteRecords( actorsList );
}
The problem with String.Join is that you are not handling the case of a comma already existing in the value. When a comma exists then you surround the value in Quotes and replace all existing Quotes with double Quotes.
String.Join(",",{"this value has a , in it","This one doesn't", "This one , does"});
See CSV Module
http://cc.davelozinski.com/c-sharp/the-fastest-way-to-read-and-process-text-files
This website did some extensive testing about how to write to a file using buffered writer, reading line by line seems to be the best way, using string builder was one of the slowest.
I use his techniques a great deal for writing stuff to file it works well.
A general purpose ToCsv() extension method:
Supports Int16/32/64, float, double, decimal, and anything supporting
ToString()
Optional custom join separator
Optional custom selector
Optional null/empty handling specification (*Opt() overloads)
Usage Examples:
"123".ToCsv() // "1,2,3"
"123".ToCsv(", ") // "1, 2, 3"
new List<int> { 1, 2, 3 }.ToCsv() // "1,2,3"
new List<Tuple<int, string>>
{
Tuple.Create(1, "One"),
Tuple.Create(2, "Two")
}
.ToCsv(t => t.Item2); // "One,Two"
((string)null).ToCsv() // throws exception
((string)null).ToCsvOpt() // ""
((string)null).ToCsvOpt(ReturnNullCsv.WhenNull) // null
Implementation
/// <summary>
/// Specifies when ToCsv() should return null. Refer to ToCsv() for IEnumerable[T]
/// </summary>
public enum ReturnNullCsv
{
/// <summary>
/// Return String.Empty when the input list is null or empty.
/// </summary>
Never,
/// <summary>
/// Return null only if input list is null. Return String.Empty if list is empty.
/// </summary>
WhenNull,
/// <summary>
/// Return null when the input list is null or empty
/// </summary>
WhenNullOrEmpty,
/// <summary>
/// Throw if the argument is null
/// </summary>
ThrowIfNull
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
this IEnumerable<T> values,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, null /*selector*/, ReturnNullCsv.ThrowIfNull, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsv<T>(
this IEnumerable<T> values,
Func<T, string> selector,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, selector, ReturnNullCsv.ThrowIfNull, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
this IEnumerable<T> values,
ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
string joinSeparator = ",")
{
return ToCsvOpt<T>(values, null /*selector*/, returnNullCsv, joinSeparator);
}
/// <summary>
/// Converts IEnumerable list of values to a comma separated string values.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="values">The values.</param>
/// <param name="selector">An optional selector</param>
/// <param name="returnNullCsv">Return mode (refer to enum ReturnNullCsv).</param>
/// <param name="joinSeparator"></param>
/// <returns>System.String.</returns>
public static string ToCsvOpt<T>(
this IEnumerable<T> values,
Func<T, string> selector,
ReturnNullCsv returnNullCsv = ReturnNullCsv.Never,
string joinSeparator = ",")
{
switch (returnNullCsv)
{
case ReturnNullCsv.Never:
if (!values.AnyOpt())
return string.Empty;
break;
case ReturnNullCsv.WhenNull:
if (values == null)
return null;
break;
case ReturnNullCsv.WhenNullOrEmpty:
if (!values.AnyOpt())
return null;
break;
case ReturnNullCsv.ThrowIfNull:
if (values == null)
throw new ArgumentOutOfRangeException("ToCsvOpt was passed a null value with ReturnNullCsv = ThrowIfNull.");
break;
default:
throw new ArgumentOutOfRangeException("returnNullCsv", returnNullCsv, "Out of range.");
}
if (selector == null)
{
if (typeof(T) == typeof(Int16) ||
typeof(T) == typeof(Int32) ||
typeof(T) == typeof(Int64))
{
selector = (v) => Convert.ToInt64(v).ToStringInvariant();
}
else if (typeof(T) == typeof(decimal))
{
selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
}
else if (typeof(T) == typeof(float) ||
typeof(T) == typeof(double))
{
selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
}
else
{
selector = (v) => v.ToString();
}
}
return String.Join(joinSeparator, values.Select(v => selector(v)));
}
public static string ToStringInvariantOpt(this Decimal? d)
{
return d.HasValue ? d.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Decimal d)
{
return d.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int64? l)
{
return l.HasValue ? l.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int64 l)
{
return l.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int32? i)
{
return i.HasValue ? i.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int32 i)
{
return i.ToString(CultureInfo.InvariantCulture);
}
public static string ToStringInvariantOpt(this Int16? i)
{
return i.HasValue ? i.Value.ToStringInvariant() : null;
}
public static string ToStringInvariant(this Int16 i)
{
return i.ToString(CultureInfo.InvariantCulture);
}
Here is my extension method, it returns a string for simplicity but my implementation writes the file to a data lake.
It provides for any delimiter, adds quotes to string (in case they contain the delimiter) and deals will nulls and blanks.
/// <summary>
/// A class to hold extension methods for C# Lists
/// </summary>
public static class ListExtensions
{
/// <summary>
/// Convert a list of Type T to a CSV
/// </summary>
/// <typeparam name="T">The type of the object held in the list</typeparam>
/// <param name="items">The list of items to process</param>
/// <param name="delimiter">Specify the delimiter, default is ,</param>
/// <returns></returns>
public static string ToCsv<T>(this List<T> items, string delimiter = ",")
{
Type itemType = typeof(T);
var props = itemType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name);
var csv = new StringBuilder();
// Write Headers
csv.AppendLine(string.Join(delimiter, props.Select(p => p.Name)));
// Write Rows
foreach (var item in items)
{
// Write Fields
csv.AppendLine(string.Join(delimiter, props.Select(p => GetCsvFieldasedOnValue(p, item))));
}
return csv.ToString();
}
/// <summary>
/// Provide generic and specific handling of fields
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="p"></param>
/// <param name="item"></param>
/// <returns></returns>
private static object GetCsvFieldasedOnValue<T>(PropertyInfo p, T item)
{
string value = "";
try
{
value = p.GetValue(item, null)?.ToString();
if (value == null) return "NULL"; // Deal with nulls
if (value.Trim().Length == 0) return ""; // Deal with spaces and blanks
// Guard strings with "s, they may contain the delimiter!
if (p.PropertyType == typeof(string))
{
value = string.Format("\"{0}\"", value);
}
}
catch (Exception ex)
{
throw ex;
}
return value;
}
}
Usage:
// Tab Delimited (TSV)
var csv = MyList.ToCsv<MyClass>("\t");
The other answers work, but my issue is loading unknown data from the database, so I needed something a bit more robust than what's already here.
I wanted something that fit the following requirements:
able to be opened in excel
had to be able to handle date time formats in an excel compatible way
had to automatically exclude linked entities (EF navigation properties)
had to support column contents containing " and the delimiter ,
had to support nullable columns
had to support a wide array of data types
numbers of every kind
guids
datetimes
custom type definitions (ie name from a linked entity)
I used month/day/year formats for the date exports for compatibility reasons
public static IReadOnlyDictionary<System.Type, Func<object, string>> CsvTypeFormats = new Dictionary<System.Type, Func<object, string>> {
// handles escaping column delimiter (',') and quote marks
{ typeof(string), x => string.IsNullOrWhiteSpace(x as string) ? null as string : $"\"{(x as string).Replace("\"", "\"\"")}\""},
{ typeof(DateTime), x => $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTime?), x => x == null ? "" : $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTimeOffset), x => $"{x:M/d/yyyy H:m:s.fff}" },
{ typeof(DateTimeOffset?), x => x == null ? "" : $"{x:M/d/yyyy H:m:s.fff}" },
};
public void WriteCsvContent<T>(ICollection<T> data, StringBuilder writer, IDictionary<System.Type, Func<object, string>> explicitMapping = null)
{
var typeMappings = CsvTypeFormats.ToDictionary(x=>x.Key, x=>x.Value);
if (explicitMapping != null) {
foreach(var mapping in explicitMapping) {
typeMappings[mapping.Key] = mapping.Value;
}
}
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => IsSimpleType(x.PropertyType))
.ToList();
// header row
writer.AppendJoin(',', props.Select(x => x.Name));
writer.AppendLine();
foreach (var item in data)
{
writer.AppendJoin(',',
props.Select(prop => typeMappings.ContainsKey(prop.PropertyType)
? typeMappings[prop.PropertyType](prop.GetValue(item))
: prop.GetValue(item)?.ToString() ?? ""
)
// escaping and special characters
.Select(x => x != null && x != "" ? $"\"{x.Replace("\"", "\"\"")}\"" : null)
);
writer.AppendLine();
}
}
private bool IsSimpleType(System.Type t)
{
return
t.IsPrimitive ||
t.IsValueType ||
t.IsEnum ||
(t == typeof(string)) ||
CsvTypeFormats.ContainsKey(t);
}
If your class uses fields instead of properties, change the GetProperties to GetFields and the PropertyType accessors to FieldType