I am working on a simple ORM for my company and have used reflection for automatic property population from queries to this point. That is obviously quite slow and I would like to improve that performance with Expression Trees. I spent a good amount of time following examples and reading into the appropriate calls and my code compiles and executes now! However, I have a MethodCallExpression that performs basic DBNull checks and the likes that appears to be working properly, but when the value is assigned to the property it sends the types default value.
First up the actual code:
public static void PopulateFromReaderUsingExpression(Descriptor descriptor, IDataRecord reader)
{
if (descriptor == null)
throw new ArgumentNullException("descriptor");
if (reader == null)
throw new ArgumentNullException("reader");
string[] AvailableColumnsInReader = new string[reader.FieldCount];
for (int i = 0; i <= reader.FieldCount - 1; i++)
{
AvailableColumnsInReader[i] = reader.GetName(i).ToUpper(CultureInfo.InvariantCulture);
}
var statements = new List<Expression>();
ParameterExpression readerExp = Expression.Parameter(typeof(IDataRecord));
ParameterExpression descriptorExp = Expression.Variable(descriptor.GetDescriptorType(), "descriptor");
BinaryExpression createInstanceExp = Expression.Assign(
descriptorExp, Expression.New(descriptor.GetDescriptorType()));
statements.Add(createInstanceExp);
foreach (KeyValuePair<PropertyInfo, PropertyMapping> pair in descriptor.ReadablePropertiesAndDataNames
.Where(property => AvailableColumnsInReader.Contains(property.Value.ReturnName.ToUpper(CultureInfo.InvariantCulture))))
{
MemberExpression propertyExp = Expression.Property(descriptorExp, pair.Key);
IndexExpression readValue =
Expression.MakeIndex(readerExp, typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }),
new[] { Expression.Constant(pair.Value.ReturnName) });
MethodCallExpression castValueExp =
Expression.Call(typeof(Descriptor)
.GetMethod("CastValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[]
{
typeof(Type), typeof(object), typeof(object)
},
null), Expression.Constant(pair.Key.PropertyType, typeof(Type)), readValue, Expression.Constant(null));
BinaryExpression assignmentExp = Expression.Assign(propertyExp, Expression.Convert(castValueExp, pair.Key.PropertyType));
statements.Add(assignmentExp);
}
var body = Expression.Block(new ParameterExpression[] { descriptorExp }, statements);
Expression.Lambda<Action<IDataRecord>>(body, readerExp).Compile()(reader);
}
The method that is being call by the MethodCallExpression and its overload:
/// <summary>
/// Detects if a value is DBNull, null, or has value.
/// </summary>
/// <param name="newType">The new type.</param>
/// <param name="value">The value.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="typeName">Name of the type from the database (used for date/time to string conversion).</param>
/// <returns>Value as type T if value is not DBNull, null, or invalid cast; otherwise defaultValue.</returns>
public static object CastValue(Type newType, object value, object defaultValue, string typeName)
{
object returnValue;
if (value is DBNull || value == null)
returnValue = defaultValue;
else if (newType == typeof(bool) && (value.GetType() == typeof(Int16) || value.GetType() == typeof(Int32)))
returnValue = ((object)(int.Parse(value.ToString(), CultureInfo.InvariantCulture) > 0 ? true : false));
else if (newType == typeof(int) && value.GetType() == typeof(long))
returnValue = ((object)((int)((long)value)));
else if (newType == typeof(int) && value.GetType() == typeof(decimal))
returnValue = ((object)((int)((decimal)value)));
else if (newType == typeof(string))
{
returnValue = value.ToString();
if (!string.IsNullOrEmpty(typeName))
if (typeName == "date")
returnValue = ((DateTime)value).ToString("MM/dd/yyyy", CultureInfo.CurrentCulture);
}
else
returnValue = value;
return returnValue;
}
/// <summary>
/// Casts the value.
/// </summary>
/// <param name="newType">The new type.</param>
/// <param name="value">The value.</param>
/// <param name="defaultValue">The default value.</param>
/// <returns>System.Object.</returns>
public static object CastValue(Type newType, object value, object defaultValue)
{
return CastValue(newType, value, defaultValue, null);
}
I have confirmed that values are making into the method and returned, but are the lost along the way, my guess is in the assignment operation.
Here I will include relevant method signatures used in the code example:
public virtual Dictionary<PropertyInfo, PropertyMapping> ReadablePropertiesAndDataNames
/// <summary>
/// THe object Orochi CRUDE uses to map properties to database actions.
/// </summary>
public class PropertyMapping
{
/// <summary>
/// Initializes a new instance of the <see cref="PropertyMapping" /> class.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="parameterName">Name of the parameter.</param>
/// <param name="returnName">Name of the return.</param>
/// <exception cref="System.ArgumentNullException">property</exception>
public PropertyMapping(PropertyInfo property, string returnName)
{
if (property == null)
throw new ArgumentNullException("property");
Contract.EndContractBlock();
this.ReturnName = !string.IsNullOrWhiteSpace(returnName) ? returnName : property.Name;
}
/// <summary>
/// Gets the name of the return.
/// </summary>
/// <value>
/// The name of the return.
/// </value>
public string ReturnName { get; private set; }
}
Here is the DebugInfo from my statement block:
.Block(Models.Billing.Claims.BillingQueue $descriptor) {
$descriptor = .New Models.Billing.Claims.BillingQueue();
$descriptor.ClaimWorkQueueId = (System.Int32).Call Orochi.CrudeData.Descriptor.CastValue(
.Constant<System.Type>(System.Int32),
$var1.Item["ClaimWorkQueueId"],
null);
$descriptor.WorkQueueName = (System.String).Call Orochi.CrudeData.Descriptor.CastValue(
.Constant<System.Type>(System.String),
$var1.Item["WorkQueueName"],
null);
$descriptor.Count = (System.Int32).Call Orochi.CrudeData.Descriptor.CastValue(
.Constant<System.Type>(System.Int32),
$var1.Item["ClaimCount"],
null)
}
I missed that I was creating a new instance of an object and setting values to it instead of by reference. I added the return statement and modified the code to use a return value.
/// <summary>
/// Builds the mapping expression.
/// </summary>
/// <param name="descriptor">The descriptor.</param>
/// <param name="reader">The reader.</param>
/// <returns>LambdaExpression.</returns>
public static Expression<Func<IDataRecord, object>> BuildMappingExpression(IDescriptor descriptor, IDataRecord reader, string[] availableColumns)
{
var statements = new List<Expression>();
ParameterExpression readerExp = Expression.Parameter(typeof(IDataRecord));
ParameterExpression descriptorExp = Expression.Variable(descriptor.GetDescriptorType(), "descriptor");
BinaryExpression createInstanceExp = Expression.Assign(
descriptorExp, Expression.New(descriptor.GetDescriptorType()));
statements.Add(createInstanceExp);
foreach (KeyValuePair<PropertyInfo, PropertyMapping> pair in descriptor.ReadablePropertiesAndDataNames
.Where(property => availableColumns.Contains(property.Value.ReturnName.ToUpper(CultureInfo.InvariantCulture))))
{
MemberExpression propertyExp = Expression.Property(descriptorExp, pair.Key);
IndexExpression readValue =
Expression.MakeIndex(readerExp, typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }),
new[] { Expression.Constant(pair.Value.ReturnName) });
MethodCallExpression castValueExp =
Expression.Call(typeof(Descriptor)
.GetMethod("CastValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[]
{
typeof(Type), typeof(object), typeof(object)
},
null), Expression.Constant(pair.Key.PropertyType, typeof(Type)), readValue, Expression.Constant(null));
BinaryExpression assignmentExp = Expression.Assign(propertyExp, Expression.Convert(castValueExp, pair.Key.PropertyType));
statements.Add(assignmentExp);
}
statements.Add(descriptorExp);
var body = Expression.Block(new ParameterExpression[] { descriptorExp }, statements);
return Expression.Lambda<Func<IDataRecord, object>>(body, readerExp);
}
Related
I am using ADO.NET over Entity Framework to get high performance as data complexity of my application is bit high. Hence confused with below two approaches:
Approach 1 - Generic Method For all
I am using below generic method for converting data table to appropriate model object. Here it uses reflection and matches all properties of model with data table headers for map and build appropriate list.
#region ConvertToList
/// <summary>
/// DataTableToList --- This function is used to Convert DataTable to List
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static List<T> DataTableToList<T>(DataTable dtDataCollection) where T : new()
{
var objList = new List<T>();
//Define what attributes to be read from the class
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;
//Read Attribute Names and Types
var objFieldNames = typeof(T).GetProperties(flags).Cast<PropertyInfo>().
Select(item => new
{
Name = item.Name.ToLower(),
Type = Nullable.GetUnderlyingType(item.PropertyType) ?? item.PropertyType
}).ToList();
//Read Datatable column names and types
var dtlFieldNames = dtDataCollection.Columns.Cast<DataColumn>().
Select(item => new
{
Name = item.ColumnName.ToLower(),
Type = item.DataType
}).ToList();
foreach (DataRow dataRow in dtDataCollection.AsEnumerable().ToList())
{
var classObj = new T();
foreach (var dtField in dtlFieldNames)
{
PropertyInfo propertyInfos = classObj.GetType().GetProperty(dtField.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var field = objFieldNames.Find(x => x.Name == dtField.Name);
if (field != null)
{
if (propertyInfos.PropertyType == typeof(DateTime))
{
propertyInfos.SetValue
(classObj, convertToDateTime(dataRow[dtField.Name]), null);
}
else if (propertyInfos.PropertyType == typeof(int))
{
propertyInfos.SetValue
(classObj, ConvertToInt(dataRow[dtField.Name]), null);
}
else if (propertyInfos.PropertyType == typeof(long))
{
propertyInfos.SetValue
(classObj, ConvertToLong(dataRow[dtField.Name]), null);
}
else if (propertyInfos.PropertyType == typeof(decimal))
{
propertyInfos.SetValue
(classObj, ConvertToDecimal(dataRow[dtField.Name]), null);
}
else if (propertyInfos.PropertyType == typeof(Boolean))
{
propertyInfos.SetValue
(classObj, ConvertToBool(dataRow[dtField.Name]), null);
}
else if (propertyInfos.PropertyType == typeof(String))
{
if (dataRow[dtField.Name].GetType() == typeof(DateTime))
{
propertyInfos.SetValue
(classObj, ConvertToDateString(dataRow[dtField.Name]), null);
}
else
{
propertyInfos.SetValue
(classObj, ConvertToString(dataRow[dtField.Name]), null);
}
}
}
}
objList.Add(classObj);
}
return objList;
}
/// <summary>
/// ConvertToDateString --- This function is used to convert object to DateString
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static string ConvertToDateString(object dtValue)
{
if (dtValue == null && dtValue == DBNull.Value)
return string.Empty;
//return SpecialDateTime.ConvertDate(Convert.ToDateTime(date));
return Convert.ToString(dtValue);
}
/// <summary>
/// ConvertToString --- This function is used to convert object to string
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static string ConvertToString(object strValue)
{
// return Convert.ToString(HelperFunctions.ReturnEmptyIfNull(value));
string returnValue = string.Empty;
if (strValue != null && strValue != DBNull.Value)
returnValue = Convert.ToString(strValue);
return returnValue;
}
/// <summary>
/// ConvertToInt --- This function is used to convert object to Int
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static int ConvertToInt(object iValue)
{
//return Convert.ToInt32(HelperFunctions.ReturnZeroIfNull(value));
int returnValue = 0;
if (iValue != null && iValue != DBNull.Value)
returnValue = Convert.ToInt32(iValue);
return returnValue;
}
/// <summary>
/// ConvertToLong ---This function is used to convert object to Long
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static long ConvertToLong(object lngValue)
{
//return Convert.ToInt64(HelperFunctions.ReturnZeroIfNull(value));
Int64 returnValue = 0;
if (lngValue != null && lngValue != DBNull.Value)
returnValue = Convert.ToInt64(lngValue);
return returnValue;
}
/// <summary>
/// ConvertToDecimal --- This function is used to convert object to Decimal
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static decimal ConvertToDecimal(object decValue)
{
//return Convert.ToDecimal(HelperFunctions.ReturnZeroIfNull(value));
decimal returnValue = 0;
if (decValue != null && decValue != DBNull.Value)
returnValue = Convert.ToDecimal(decValue);
return returnValue;
}
/// <summary>
/// DateTime --- This function is used to convert object to convertToDateTime
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static DateTime? convertToDateTime(object dtValue)
{
// return Convert.ToDateTime(HelperFunctions.ReturnDateTimeMinIfNull(date));
DateTime? returnValue = null;
if (dtValue != null && dtValue != DBNull.Value)
returnValue = Convert.ToDateTime(dtValue);
return returnValue;
}
/// <summary>
/// ConvertToBool ---This function is used to convert object to Bool
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static bool ConvertToBool(object blValue)
{
//return Convert.ToDecimal(HelperFunctions.ReturnZeroIfNull(value));
bool returnValue = false;
if (blValue != null && blValue != DBNull.Value)
returnValue = Convert.ToBoolean(blValue);
return returnValue;
}
#endregion ConvertToList
Approach 2 - By Manual mapping like below
Manual mapping to model object from data table. In this approach we need to write this method for every mapping.
List<Student> studentList = new List<Student>();
for (int i = 0; i < dt.Rows.Count; i++)
{
Student student = new Student();
student.StudentId = Convert .ToInt32 (dt.Rows[i]["StudentId"]);
student.StudentName = dt.Rows[i]["StudentName"].ToString();
student.Address = dt.Rows[i]["Address"].ToString();
student.MobileNo = dt.Rows[i]["MobileNo"].ToString();
studentList.Add(student);
}
My Question - Is this generic way of conversion makes impact on performance? Should I switch to normal manual mapping of objects like Approach 2? Is Approach 2 enhancing the performance ?
Answer Yes/No would not be precise.
What you need to do is to measure numbers. I recommend using performance measurement tools like dotTrace.
Create test cases for mapping single table with many rows, many small tables and table with many columns.
Run benchmark of reflection-based generic and manual methods
This would give you precise numbers of how much time you are winning using manual mapping and what are bottlenecks of generic methods.
If your win is small (lets say 10-20% of time) or works only in specific case, usage of manual mapping is not worth efforts.
Also it may help you discover weak spots of you generic method. For example discovering property
PropertyInfo propertyInfos = classObj.GetType().GetProperty(dtField.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
May be moved one level up and moved to hash table. Discovering properties for each row may be overkill.
Working with VS 2017 on console application. When I use Dictionary<string, Guid> all works fine, however, when I use Dictionary<Guid, Guid> it doesn't map to AppSettings object (dict count is 0). Is there any way around it, rather then using string and converting it to Guid in the application?
appsetting.json
{
"AppSettings": {
"DictionaryTest": {
"55789653-86A3-485C-95E8-5E23C3219130": "74F87895-4965-447A-B07F-F702573218B7",
"FB1E7891-B3C2-4E83-A6BF-7F321C11BFFA": "FA7892A9-7925-4150-82A7-5DD3213D1242"
}
}
}
in Program.cs
var appPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().CodeBase).Replace(#"file:\", "");
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile($#"{appPath}\appsettings.json");
Configuration = builder.Build();
appSettings = new AppSettings();
Configuration.GetSection("AppSettings").Bind(appSettings);
When I use Dictionary<string, Guid> scenario there are 2 items in appSettings.DictionaryTest variable.
But when I use Dictionary<Guid, Guid> scenario there are 0 items in appSettings.DictionaryTest variable.
Dictionary<string, Guid> scenario:
AppSettings.cs
public class AppSettings
{
public Dictionary<string, Guid> DictionaryTest { get; set; }
}
Dictionary<Guid, Guid> scenario:
AppSettings.cs
public class AppSettings
{
public Dictionary<Guid, Guid> DictionaryTest { get; set; }
}
At the moment, it appears that the Configuration Binder doesn't support Guid as a key type. The only supported types are string and Enum types.
This is actually stated in the source code:
if (keyType != typeof(string) && !keyTypeIsEnum)
{
// We only support string and enum keys
return;
}
But I don't think it is documented anywhere else.
While Kevin Eaton is correct and the official package doesn't support this, nothing stops you from shamelessly copying that file with the Extension methods and with just a couple of new lines of code, implement this yourself
The below works
//Install Nuget Package Microsoft.Extensions.Configuration.Binder
//Install Nuget Package Microsoft.Extensions.Configuration.Json
using System;
using System.Collections.Generic;
using Microsoft.Extensions.Configuration;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
var builder = new ConfigurationBuilder().AddJsonFile($"appsettings.json", true, true);
var configuration = builder.Build();
var appSettings = new AppSettings();
var configurationSection = configuration.GetSection("AppSettings");
configurationSection.BindWhichSupportsGuidAsKey(appSettings);
Console.WriteLine("Finished!");
}
}
public class AppSettings
{
public Dictionary<Guid, Guid> DictionaryTest { get; set; }
}
}
All I did was
Renamed the class to CustomConfigurationBinderExtesions so that the methods don't clash
Renamed the Bind method to BindWhichSupportsGuidAsKey
Change if (keyType != typeof(string) && !keyTypeIsEnum) to
if (keyType != typeof(string) && keyType != typeof(Guid) && !keyTypeIsEnum)
Added this
else if (keyType == typeof(Guid))
{
Guid key = Guid.Parse(child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
Below is the full file(The original is here)
(You can remove most of the code here and keep only what you need but I'm too lazy to do that :))
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace Microsoft.Extensions.Configuration
{
/// <summary>
/// Static helper class that allows binding strongly typed objects to configuration values.
/// </summary>
public static class CustomConfigurationBinderExtesions
{
private const BindingFlags DeclaredOnlyLookup = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <typeparam name="T">The type of the new instance to bind.</typeparam>
/// <param name="configuration">The configuration instance to bind.</param>
/// <returns>The new instance of T if successful, default(T) otherwise.</returns>
//public static T Get<T>(this IConfiguration configuration)
// => configuration.Get<T>(_ => { });
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <typeparam name="T">The type of the new instance to bind.</typeparam>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
/// <returns>The new instance of T if successful, default(T) otherwise.</returns>
//public static T Get<T>(this IConfiguration configuration, Action<BinderOptions> configureOptions)
//{
// if (configuration == null)
// {
// throw new ArgumentNullException(nameof(configuration));
// }
// object result = configuration.Get(typeof(T), configureOptions);
// if (result == null)
// {
// return default(T);
// }
// return (T)result;
//}
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="type">The type of the new instance to bind.</param>
/// <returns>The new instance if successful, null otherwise.</returns>
//public static object Get(this IConfiguration configuration, Type type)
// => configuration.Get(type, _ => { });
/// <summary>
/// Attempts to bind the configuration instance to a new instance of type T.
/// If this configuration section has a value, that will be used.
/// Otherwise binding by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="type">The type of the new instance to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
/// <returns>The new instance if successful, null otherwise.</returns>
public static object Get(this IConfiguration configuration, Type type, Action<BinderOptions> configureOptions)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
var options = new BinderOptions();
configureOptions?.Invoke(options);
return BindInstance(type, instance: null, config: configuration, options: options);
}
/// <summary>
/// Attempts to bind the given object instance to the configuration section specified by the key by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="key">The key of the configuration section to bind.</param>
/// <param name="instance">The object to bind.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, string key, object instance)
=> configuration.GetSection(key).BindWhichSupportsGuidAsKey(instance);
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, object instance)
=> configuration.BindWhichSupportsGuidAsKey(instance, o => { });
/// <summary>
/// Attempts to bind the given object instance to configuration values by matching property names against configuration keys recursively.
/// </summary>
/// <param name="configuration">The configuration instance to bind.</param>
/// <param name="instance">The object to bind.</param>
/// <param name="configureOptions">Configures the binder options.</param>
public static void BindWhichSupportsGuidAsKey(this IConfiguration configuration, object instance, Action<BinderOptions> configureOptions)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
if (instance != null)
{
var options = new BinderOptions();
configureOptions?.Invoke(options);
BindInstance(instance.GetType(), instance, configuration, options);
}
}
/// <summary>
/// Extracts the value with the specified key and converts it to type T.
/// </summary>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <returns>The converted value.</returns>
public static T GetValue<T>(this IConfiguration configuration, string key)
{
return GetValue(configuration, key, default(T));
}
/// <summary>
/// Extracts the value with the specified key and converts it to type T.
/// </summary>
/// <typeparam name="T">The type to convert the value to.</typeparam>
/// <param name="configuration">The configuration.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns>The converted value.</returns>
public static T GetValue<T>(this IConfiguration configuration, string key, T defaultValue)
{
return (T)GetValue(configuration, typeof(T), key, defaultValue);
}
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="type">The type to convert the value to.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <returns>The converted value.</returns>
public static object GetValue(this IConfiguration configuration, Type type, string key)
{
return GetValue(configuration, type, key, defaultValue: null);
}
/// <summary>
/// Extracts the value with the specified key and converts it to the specified type.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="type">The type to convert the value to.</param>
/// <param name="key">The key of the configuration section's value to convert.</param>
/// <param name="defaultValue">The default value to use if no value is found.</param>
/// <returns>The converted value.</returns>
public static object GetValue(this IConfiguration configuration, Type type, string key, object defaultValue)
{
IConfigurationSection section = configuration.GetSection(key);
string value = section.Value;
if (value != null)
{
return ConvertValue(type, value, section.Path);
}
return defaultValue;
}
private static void BindNonScalar(this IConfiguration configuration, object instance, BinderOptions options)
{
if (instance != null)
{
foreach (PropertyInfo property in GetAllProperties(instance.GetType()))
{
BindProperty(property, instance, configuration, options);
}
}
}
private static void BindProperty(PropertyInfo property, object instance, IConfiguration config, BinderOptions options)
{
// We don't support set only, non public, or indexer properties
if (property.GetMethod == null ||
(!options.BindNonPublicProperties && !property.GetMethod.IsPublic) ||
property.GetMethod.GetParameters().Length > 0)
{
return;
}
object propertyValue = property.GetValue(instance);
bool hasSetter = property.SetMethod != null && (property.SetMethod.IsPublic || options.BindNonPublicProperties);
if (propertyValue == null && !hasSetter)
{
// Property doesn't have a value and we cannot set it so there is no
// point in going further down the graph
return;
}
propertyValue = BindInstance(property.PropertyType, propertyValue, config.GetSection(property.Name), options);
if (propertyValue != null && hasSetter)
{
property.SetValue(instance, propertyValue);
}
}
private static object BindToCollection(Type type, IConfiguration config, BinderOptions options)
{
Type genericType = typeof(List<>).MakeGenericType(type.GenericTypeArguments[0]);
object instance = Activator.CreateInstance(genericType);
BindCollection(instance, genericType, config, options);
return instance;
}
// Try to create an array/dictionary instance to back various collection interfaces
private static object AttemptBindToCollectionInterfaces(Type type, IConfiguration config, BinderOptions options)
{
if (!type.IsInterface)
{
return null;
}
Type collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyList<>), type);
if (collectionInterface != null)
{
// IEnumerable<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyDictionary<,>), type);
if (collectionInterface != null)
{
Type dictionaryType = typeof(Dictionary<,>).MakeGenericType(type.GenericTypeArguments[0], type.GenericTypeArguments[1]);
object instance = Activator.CreateInstance(dictionaryType);
BindDictionary(instance, dictionaryType, config, options);
return instance;
}
collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
object instance = Activator.CreateInstance(typeof(Dictionary<,>).MakeGenericType(type.GenericTypeArguments[0], type.GenericTypeArguments[1]));
BindDictionary(instance, collectionInterface, config, options);
return instance;
}
collectionInterface = FindOpenGenericInterface(typeof(IReadOnlyCollection<>), type);
if (collectionInterface != null)
{
// IReadOnlyCollection<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
// ICollection<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
collectionInterface = FindOpenGenericInterface(typeof(IEnumerable<>), type);
if (collectionInterface != null)
{
// IEnumerable<T> is guaranteed to have exactly one parameter
return BindToCollection(type, config, options);
}
return null;
}
private static object BindInstance(Type type, object instance, IConfiguration config, BinderOptions options)
{
// if binding IConfigurationSection, break early
if (type == typeof(IConfigurationSection))
{
return config;
}
var section = config as IConfigurationSection;
string configValue = section?.Value;
object convertedValue;
Exception error;
if (configValue != null && TryConvertValue(type, configValue, section.Path, out convertedValue, out error))
{
if (error != null)
{
throw error;
}
// Leaf nodes are always reinitialized
return convertedValue;
}
if (config != null && config.GetChildren().Any())
{
// If we don't have an instance, try to create one
if (instance == null)
{
// We are already done if binding to a new collection instance worked
instance = AttemptBindToCollectionInterfaces(type, config, options);
if (instance != null)
{
return instance;
}
instance = CreateInstance(type);
}
// See if its a Dictionary
Type collectionInterface = FindOpenGenericInterface(typeof(IDictionary<,>), type);
if (collectionInterface != null)
{
BindDictionary(instance, collectionInterface, config, options);
}
else if (type.IsArray)
{
instance = BindArray((Array)instance, config, options);
}
else
{
// See if its an ICollection
collectionInterface = FindOpenGenericInterface(typeof(ICollection<>), type);
if (collectionInterface != null)
{
BindCollection(instance, collectionInterface, config, options);
}
// Something else
else
{
BindNonScalar(config, instance, options);
}
}
}
return instance;
}
private static object CreateInstance(Type type)
{
if (type.IsInterface || type.IsAbstract)
{
throw new InvalidOperationException("SR.Format(SR.Error_CannotActivateAbstractOrInterface, type)");
}
if (type.IsArray)
{
if (type.GetArrayRank() > 1)
{
throw new InvalidOperationException("SR.Format(SR.Error_UnsupportedMultidimensionalArray, type)");
}
return Array.CreateInstance(type.GetElementType(), 0);
}
if (!type.IsValueType)
{
bool hasDefaultConstructor = type.GetConstructors(DeclaredOnlyLookup).Any(ctor => ctor.IsPublic && ctor.GetParameters().Length == 0);
if (!hasDefaultConstructor)
{
throw new InvalidOperationException("SR.Format(SR.Error_MissingParameterlessConstructor, type)");
}
}
try
{
return Activator.CreateInstance(type);
}
catch (Exception ex)
{
throw new InvalidOperationException("SR.Format(SR.Error_FailedToActivate, type), ex");
}
}
private static void BindDictionary(object dictionary, Type dictionaryType, IConfiguration config, BinderOptions options)
{
// IDictionary<K,V> is guaranteed to have exactly two parameters
Type keyType = dictionaryType.GenericTypeArguments[0];
Type valueType = dictionaryType.GenericTypeArguments[1];
bool keyTypeIsEnum = keyType.IsEnum;
if (keyType != typeof(string) && keyType != typeof(Guid) && !keyTypeIsEnum)
{
// We only support string and enum keys
return;
}
PropertyInfo setter = dictionaryType.GetProperty("Item", DeclaredOnlyLookup);
foreach (IConfigurationSection child in config.GetChildren())
{
object item = BindInstance(
type: valueType,
instance: null,
config: child,
options: options);
if (item != null)
{
if (keyType == typeof(string))
{
string key = child.Key;
setter.SetValue(dictionary, item, new object[] { key });
}
else if (keyType == typeof(Guid))
{
Guid key = Guid.Parse(child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
else if (keyTypeIsEnum)
{
object key = Enum.Parse(keyType, child.Key);
setter.SetValue(dictionary, item, new object[] { key });
}
}
}
}
private static void BindCollection(object collection, Type collectionType, IConfiguration config, BinderOptions options)
{
// ICollection<T> is guaranteed to have exactly one parameter
Type itemType = collectionType.GenericTypeArguments[0];
MethodInfo addMethod = collectionType.GetMethod("Add", DeclaredOnlyLookup);
foreach (IConfigurationSection section in config.GetChildren())
{
try
{
object item = BindInstance(
type: itemType,
instance: null,
config: section,
options: options);
if (item != null)
{
addMethod.Invoke(collection, new[] { item });
}
}
catch
{
}
}
}
private static Array BindArray(Array source, IConfiguration config, BinderOptions options)
{
IConfigurationSection[] children = config.GetChildren().ToArray();
int arrayLength = source.Length;
Type elementType = source.GetType().GetElementType();
var newArray = Array.CreateInstance(elementType, arrayLength + children.Length);
// binding to array has to preserve already initialized arrays with values
if (arrayLength > 0)
{
Array.Copy(source, newArray, arrayLength);
}
for (int i = 0; i < children.Length; i++)
{
try
{
object item = BindInstance(
type: elementType,
instance: null,
config: children[i],
options: options);
if (item != null)
{
newArray.SetValue(item, arrayLength + i);
}
}
catch
{
}
}
return newArray;
}
private static bool TryConvertValue(Type type, string value, string path, out object result, out Exception error)
{
error = null;
result = null;
if (type == typeof(object))
{
result = value;
return true;
}
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
if (string.IsNullOrEmpty(value))
{
return true;
}
return TryConvertValue(Nullable.GetUnderlyingType(type), value, path, out result, out error);
}
TypeConverter converter = TypeDescriptor.GetConverter(type);
if (converter.CanConvertFrom(typeof(string)))
{
try
{
result = converter.ConvertFromInvariantString(value);
}
catch (Exception ex)
{
error = new InvalidOperationException("SR.Format(SR.Error_FailedBinding, path, type), ex");
}
return true;
}
if (type == typeof(byte[]))
{
try
{
result = Convert.FromBase64String(value);
}
catch (FormatException ex)
{
error = new InvalidOperationException("SR.Format(SR.Error_FailedBinding, path, type), ex");
}
return true;
}
return false;
}
private static object ConvertValue(Type type, string value, string path)
{
object result;
Exception error;
TryConvertValue(type, value, path, out result, out error);
if (error != null)
{
throw error;
}
return result;
}
private static Type FindOpenGenericInterface(Type expected, Type actual)
{
if (actual.IsGenericType &&
actual.GetGenericTypeDefinition() == expected)
{
return actual;
}
Type[] interfaces = actual.GetInterfaces();
foreach (Type interfaceType in interfaces)
{
if (interfaceType.IsGenericType &&
interfaceType.GetGenericTypeDefinition() == expected)
{
return interfaceType;
}
}
return null;
}
private static IEnumerable<PropertyInfo> GetAllProperties(Type type)
{
var allProperties = new List<PropertyInfo>();
do
{
allProperties.AddRange(type.GetProperties(DeclaredOnlyLookup));
type = type.BaseType;
}
while (type != typeof(object));
return allProperties;
}
}
}
Try getting DictionaryTest as JSON and converting it to a Dictionary<Guid, Guid> manually with a JSON library. I wouldn't doubt if the conversion from appSettings isn't designed to handle that type of cast out of the box. Newtonsoft I know for sure can convert from JSON to a dictionary with a Guid key though. Right now I'm thinking something like this (using newtonsoft):
public class AppSettings
{
public JObject DictionaryTest { get; set; }
public Dictionary<Guid, Guid> DictionaryTestConvert
{
get { return JObject.toObject<Dictionary<Guid, Guid>>(DictionaryTestJson); }
}
}
I have searched endlessly for a thread that specifies exactly my question but I cannot find it.
I want to take a class property and just take the property out of it as a string!
public class Foo
{
public Foo ()
{
}
public int MyProperty { get; set; }
}
Then I want to have the string "MyProperty" in the end, something like this:
Foo f = new Foo();
string s = Helperfunction(f.MyProperty);
string Helperfunction( X )
{
string MyString;
//do something with X
return MyString;
}
How difficult would this be to realize just with maybe a helperfunction?
So the output should be "MyProperty"
Because I dont want to hard code it, and as soon as I would ever refactor the property name, it would still work without editing the hardcoded string!
You can use reflection to get the name, here is a helper class I use:
public static class MemberName
{
/// <summary>
/// *** WARNING - Uses reflection - use responsibly ***
/// </summary>
/// <param name="instance"></param>
/// <param name="expression"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static string GetMemberName<T>(this T instance, Expression<Func<T, object>> expression)
{
return GetMemberName(expression);
}
/// <summary>
/// *** WARNING - Uses reflection - use responsibly ***
/// </summary>
/// <param name="expression"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static string GetMemberName<T>(Expression<Func<T, object>> expression)
{
if (expression == null)
{
throw new ArgumentException("The expression cannot be null.");
}
return GetMemberName(expression.Body);
}
/// <summary>
/// *** WARNING - Uses reflection - use responsibly ***
/// </summary>
/// <param name="instance"></param>
/// <param name="expression"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static string GetMemberName<T>(this T instance, Expression<Action<T>> expression)
{
return GetMemberName(expression);
}
/// <summary>
/// *** WARNING - Uses reflection - use responsibly ***
/// </summary>
/// <param name="expression"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
public static string GetMemberName<T>(Expression<Action<T>> expression)
{
if (expression == null)
{
throw new ArgumentException("The expression cannot be null.");
}
return GetMemberName(expression.Body);
}
private static string GetMemberName(Expression expression)
{
if (expression == null)
{
throw new ArgumentException("The expression cannot be null.");
}
if (expression is MemberExpression)
{
// Reference type property or field
var memberExpression = (MemberExpression)expression;
return memberExpression.Member.Name;
}
if (expression is MethodCallExpression)
{
// Reference type method
var methodCallExpression = (MethodCallExpression)expression;
return methodCallExpression.Method.Name;
}
if (expression is UnaryExpression)
{
// Property, field of method returning value type
var unaryExpression = (UnaryExpression)expression;
return GetMemberName(unaryExpression);
}
throw new ArgumentException("Invalid expression");
}
private static string GetMemberName(UnaryExpression unaryExpression)
{
if (unaryExpression.Operand is MethodCallExpression)
{
var methodExpression = (MethodCallExpression)unaryExpression.Operand;
return methodExpression.Method.Name;
}
return ((MemberExpression)unaryExpression.Operand).Member.Name;
}
}
And an example usage:
string propName = MemberName.GetMemberName<Foo>(x => x.MyProperty);
I don't quite understand the question, but if you want to do something with the propertyname, you could have a look at the CallerMemberName attribute.
string Helperfunction([CallerMemberName]string X = null )
{
return "PropertyName: " + X;
}
If you call this method within a property getter, then it will output the name of the property:
public class Foo
{
public string Foo { get { Trace.WriteLine (SomeFunction()); } }
}
Will output "MyProperty"
I'll start with some classes...
The Domain Entity:
public class Account
{
public int Id { get; set; }
public double Balance { get; set; }
public string CustomerName { get; set; }
}
The View Model:
public class AccountModel
{
public int Id { get; set; }
public double Bal { get; set; }
public string Name { get; set; }
}
The Repository:
My repository has a method on it that takes an expression and returns a list, like this:
public interface IAccountRepository
{
IEnumerable<Account> Query(Expression<Func<Account, bool>> expression);
}
The Problem
My application generates an Expression<Func<AccountModel, bool>> in the UI. I need to somehow convert or map the EXPRESSION from AccountModel to Account so that I can use it in my Query method. I say "map" because, if you notice, my model and domain objects are similar, but don't necessarily have the same property names.
How can this be done?
This sounds like a job for AutoMapper. Automapper allows you to map one class to another at one point in time and use this mapping configuration later on.
See the Projection page on the wiki for the kind of thing you are after.
Update As you are using Entity Framework, here is an update for remapping your expression from using AccountModel to Account.
In the CompositionRoot of your application, set up AutoMapper like so (ignore Code Contract statements if you do not use Code Contracts):
var accountModelMap = Mapper.CreateMap<AccountModel, Account>();
Contract.Assume(accountModelMap != null);
accountModelMap.ForMember(account => account.Id, expression => expression.MapFrom(model => model.Id));
accountModelMap.ForMember(account => account.Balance, expression => expression.MapFrom(model => model.Bal));
accountModelMap.ForMember(account => account.CustomerName, expression => expression.MapFrom(model => model.Name));
This configures how the two data types relate to eachother.
Implement an ExpressionVisitor to use AutoMapper to rebind member access from one type to another.
/// <summary>
/// An <see cref="ExpressionVisitor"/> implementation which uses <see href="http://automapper.org">AutoMapper</see> to remap property access from elements of type <typeparamref name="TSource"/> to elements of type <typeparamref name="TDestination"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
public class AutoMapVisitor<TSource, TDestination> : ExpressionVisitor
{
private readonly ParameterExpression _newParameter;
private readonly TypeMap _typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
/// <summary>
/// Initialises a new instance of the <see cref="AutoMapVisitor{TSource, TDestination}"/> class.
/// </summary>
/// <param name="newParameter">The new <see cref="ParameterExpression"/> to access.</param>
public AutoMapVisitor(ParameterExpression newParameter)
{
Contract.Requires(newParameter != null);
_newParameter = newParameter;
Contract.Assume(_typeMap != null);
}
[ContractInvariantMethod]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void ObjectInvariant()
{
Contract.Invariant(_typeMap != null);
Contract.Invariant(_newParameter != null);
}
/// <summary>
/// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression"/>.
/// </summary>
/// <returns>
/// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression.
/// </returns>
/// <param name="node">The expression to visit.</param>
protected override Expression VisitMember(MemberExpression node)
{
var propertyMaps = _typeMap.GetPropertyMaps();
Contract.Assume(propertyMaps != null);
// Find any mapping for this member
var propertyMap = propertyMaps.SingleOrDefault(map => map.SourceMember == node.Member);
if (propertyMap == null)
return base.VisitMember(node);
var destinationProperty = propertyMap.DestinationProperty;
Contract.Assume(destinationProperty != null);
var destinationMember = destinationProperty.MemberInfo;
Contract.Assume(destinationMember != null);
// Check the new member is a property too
var property = destinationMember as PropertyInfo;
if (property == null)
return base.VisitMember(node);
// Access the new property
var newPropertyAccess = Expression.Property(_newParameter, property);
return base.VisitMember(newPropertyAccess);
}
}
Then implement an extension method to make this easier to use:
/// <summary>
/// A class which contains extension methods for <see cref="Expression"/> and <see cref="Expression{TDelegate}"/> instances.
/// </summary>
public static class ExpressionExtensions
{
/// <summary>
/// Remaps all property access from type <typeparamref name="TSource"/> to <typeparamref name="TDestination"/> in <paramref name="expression"/>.
/// </summary>
/// <typeparam name="TSource">The type of the source element.</typeparam>
/// <typeparam name="TDestination">The type of the destination element.</typeparam>
/// <typeparam name="TResult">The type of the result from the lambda expression.</typeparam>
/// <param name="expression">The <see cref="Expression{TDelegate}"/> to remap the property access in.</param>
/// <returns>An <see cref="Expression{TDelegate}"/> equivalent to <paramref name="expression"/>, but applying to elements of type <typeparamref name="TDestination"/> instead of <typeparamref name="TSource"/>.</returns>
public static Expression<Func<TDestination, TResult>> RemapForType<TSource, TDestination, TResult>(this Expression<Func<TSource, TResult>> expression)
{
Contract.Requires(expression != null);
Contract.Ensures(Contract.Result<Expression<Func<TDestination, TResult>>>() != null);
var newParameter = Expression.Parameter(typeof (TDestination));
Contract.Assume(newParameter != null);
var visitor = new AutoMapVisitor<TSource, TDestination>(newParameter);
var remappedBody = visitor.Visit(expression.Body);
if (remappedBody == null)
throw new InvalidOperationException("Unable to remap expression");
return Expression.Lambda<Func<TDestination, TResult>>(remappedBody, newParameter);
}
}
This can subsequently be used like so (in an NUnit test):
[TestFixture]
public class RemappingTests
{
#region Setup/Teardown
/// <summary>
/// Sets up the variables before each test.
/// </summary>
[SetUp]
public void Setup()
{
var accountModelMap = Mapper.CreateMap<AccountModel, Account>();
Contract.Assume(accountModelMap != null);
accountModelMap.ForMember(account => account.Id, expression => expression.MapFrom(model => model.Id));
accountModelMap.ForMember(account => account.Balance, expression => expression.MapFrom(model => model.Bal));
accountModelMap.ForMember(account => account.CustomerName, expression => expression.MapFrom(model => model.Name));
}
[TearDown]
public void Teardown()
{
Mapper.Reset();
}
#endregion
/// <summary>
/// Checks that <see cref="ExpressionExtensions.RemapForType{TSource, TDestination, TResult}(Expression{Func{TSource, TResult}})"/> correctly remaps all property access for the new type.
/// </summary>
/// <param name="balance">The balance to use as the value for <see cref="Account.Balance"/>.</param>
/// <returns>Whether the <see cref="Account.Balance"/> was greater than 50.</returns>
[TestCase(0, Result = false)]
[TestCase(80, Result = true)]
public bool RemapperUsesPropertiesOfNewDataType(double balance)
{
Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();
var compiled = accountExpr.Compile();
Contract.Assume(compiled != null);
var hasBalance = compiled(new Account {Balance = balance});
return hasBalance;
}
}
In case that is too much code to find the exact call, here it is:
Expression<Func<AccountModel, bool>> modelExpr = model => model.Bal > 50;
var accountExpr = modelExpr.RemapForType<AccountModel, Account, bool>();
You can use an ExpressionVisitor to rewrite the Expression:
public class AccountModelRewriter : ExpressionVisitor
{
private Stack<ParameterExpression[]> _LambdaStack = new Stack<ParameterExpression[]>();
protected override Expression VisitLambda<T>(Expression<T> node)
{
var lambda = (LambdaExpression)node;
_LambdaStack.Push(
lambda.Parameters.Select(parameter => typeof(AccountModel) == parameter.Type ? Expression.Parameter(typeof(Account)) : parameter)
.ToArray()
);
lambda = Expression.Lambda(
this.Visit(lambda.Body),
_LambdaStack.Pop()
);
return lambda;
}
protected override Expression VisitMember(MemberExpression node)
{
var memberExpression = (MemberExpression)node;
var declaringType = memberExpression.Member.DeclaringType;
var propertyName = memberExpression.Member.Name;
if (typeof(AccountModel) == declaringType)
{
switch (propertyName)
{
case "Bal" :
propertyName = "Balance";
break;
case "Name" :
propertyName = "CustomerName";
break;
}
memberExpression = Expression.Property(
this.Visit(memberExpression.Expression),
typeof(Account).GetProperty(propertyName)
);
}
return memberExpression;
}
protected override Expression VisitParameter(ParameterExpression node)
{
node = (ParameterExpression)base.VisitParameter(node);
if (typeof(AccountModel) == node.Type)
{
node = this._LambdaStack.Peek().Single(parameter => parameter.Type == typeof(Account));
}
return node;
}
}
This visitor switches the input parameters from type AccountModel to Account (that's the VisitLambda and VisitParameter methods), and also changes all property accessors to use this new parameter as well as switching the property names if appropriate (that's the VisitMember part).
Usage is as follows:
Expression<Func<AccountModel, bool>> accountModelQuery = a => a.Bal == 0 && a.Name != null && a.Id != 7;
var accountQuery = (Expression<Func<Account, bool>>)new AccountModelRewriter().Visit(accountModelQuery);
I want to parse a string into a type easily, but I don't want to write wrapper code for each type, I just want to be able to do "1234".Parse() or the like and have it return 1234. This should work for any type that has parsing capabilities.
This trick should work. It uses the type of the variable you're assigning to automagically:
public static class StringExtensions {
public static ParsedString Parse(this string s) {
return new ParsedString(s);
}
}
public class ParsedString {
string str;
public ParsedString(string s) {
str = s;
}
public static implicit operator int(ParsedString s) {
return int.Parse(s.str);
}
public static implicit operator double(ParsedString s) {
return double.Parse(s.str);
}
public static implicit operator short(ParsedString s) {
return short.Parse(s.str);
}
public static implicit operator byte(ParsedString s) {
return byte.Parse(s.str);
}
// ... add other supported types ...
}
Usage:
int p = "1234".Parse();
I would prefer explicitly parsing using framework provided methods rather than relying on these kind of tricks.
My solution works for any type that implements the static method TryParse(string, out T), whether it's a class or a struct. Also, it will work for nullable structs, e.g.
"1234".Parse<int>() == 1234
"asdf".Parse<int>() == 0 // i.e. default(int)
"1234".Parse<int?>() == 1234
"asdf".Parse<int?>() == null
"2001-02-03".Parse<DateTime?>() == new DateTime(2009, 2, 3)
and since System.Net.IPAddress has TryParse,
"127.0.0.1".Parse<IPAddress>().Equals(new IPAddress(new byte[] { 127, 0, 0, 1 }))
I create the code with the System.Linq.Expressions framework, and then cache the created lambda. Since this is done in a generic static class with a specified type, this happens only once per type to parse.
public static class StringExtensions
{
/// <summary>
/// Parse the <paramref name="target"/> as a <typeparamref name="T"/>. If this cannot be achieved, return the default value of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type to parse into.</typeparam>
/// <param name="target">The string to parse.</param>
/// <returns>The resultant <typeparamref name="T"/> or the default of <typeparamref name="T"/>.</returns>
/// <example>
/// <code>
/// "1234".Parse<int>() == 1234;
/// "a".Parse<int>() == 0;
/// "a".Parse<int?>() == null;
/// "2010-01-01".Parse<DateTime?>() == new DateTime(2010, 1, 1)
/// "2010-01-01a".Parse<DateTime?>() == null
/// "127.0.0.1".Parse<System.Net.IPAddress>().Equals(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }))
/// "".Parse<System.Net.IPAddress>() == null
/// </code>
/// </example>
public static T Parse<T>(this string target)
{
return ParseHelper<T>.Parse(target);
}
/// <summary>
/// Parse the <paramref name="target"/> as a <typeparamref name="T"/>. If this cannot be achieved, return <paramref name="defaultValue"/>
/// </summary>
/// <typeparam name="T">The type to parse into.</typeparam>
/// <param name="target">The string to parse.</param>
/// <param name="defaultValue">The value to return if <paramref name="target"/> could not be parsed.</param>
/// <returns>The resultant <typeparamref name="T"/> or <paramref name="defaultValue"/>.</returns>
/// <example>
/// <code>
/// "1234".Parse<int>(-1) == 1234;
/// "a".Parse<int>(-1) == -1;
/// "2010-01-01".Parse<DateTime?>(new DateTime(1900, 1, 1)) == new DateTime(2010, 1, 1)
/// "2010-01-01a".Parse<DateTime?>(new DateTime(1900, 1, 1)) == new DateTime(1900, 1, 1)
/// "127.0.0.1".Parse<System.Net.IPAddress>(new System.Net.IPAddress(new byte[] { 0, 0, 0, 0 })).Equals(new System.Net.IPAddress(new byte[] { 127, 0, 0, 1 }))
/// "".Parse<System.Net.IPAddress>(new System.Net.IPAddress(new byte[] { 0, 0, 0, 0 })).Equals(new System.Net.IPAddress(new byte[] { 0, 0, 0, 0 }))
/// </code>
/// </example>
public static T Parse<T>(this string target, T defaultValue)
{
return ParseHelper<T>.Parse(target, defaultValue);
}
private static class ParseHelper<T>
{
private static readonly Func<string, T, T, T> _parser;
static ParseHelper()
{
Type type = typeof(T);
bool isNullable = false;
if (type.GetGenericArguments().Length > 0 && type.GetGenericTypeDefinition() == typeof(Nullable<>))
{
isNullable = true;
type = type.GetGenericArguments()[0];
}
ParameterExpression target = Expression.Parameter(typeof(string), "target");
ParameterExpression defaultValue = Expression.Parameter(typeof(T), "defaultValue");
ParameterExpression result = Expression.Parameter(typeof(T), "result");
if (isNullable)
{
Type helper = typeof(NullableParseHelper<>);
helper = helper.MakeGenericType(typeof(T), type);
MethodInfo parseMethod = helper.GetMethod("Parse");
_parser = Expression.Lambda<Func<string, T, T, T>>(
Expression.Call(parseMethod, target, defaultValue),
target, defaultValue, result).Compile();
}
else
{
MethodInfo tryParseMethod = (from m in typeof(T).GetMethods()
where m.Name == "TryParse"
let ps = m.GetParameters()
where ps.Count() == 2
&& ps[0].ParameterType == typeof(string)
&& ps[1].ParameterType == typeof(T).MakeByRefType()
select m).SingleOrDefault();
if (tryParseMethod == null)
{
throw new InvalidOperationException(string.Format("Cannot find method {0}.TryParse(string, out {0})", type.FullName));
}
_parser = Expression.Lambda<Func<string, T, T, T>>(
Expression.Condition(
Expression.Call(tryParseMethod, target, result),
result,
defaultValue),
target, defaultValue, result).Compile();
}
}
public static T Parse(string target)
{
return _parser.Invoke(target, default(T), default(T));
}
public static T Parse(string target, T defaultValue)
{
return _parser.Invoke(target, defaultValue, default(T));
}
private static class NullableParseHelper<TBase> where TBase : struct
{
private static readonly Func<string, TBase?, TBase, TBase?> _parser;
static NullableParseHelper()
{
MethodInfo tryParseMethod = (from m in typeof(TBase).GetMethods()
where m.Name == "TryParse"
let ps = m.GetParameters()
where ps.Count() == 2
&& ps[0].ParameterType == typeof(string)
&& ps[1].ParameterType == typeof(TBase).MakeByRefType()
select m).SingleOrDefault();
if (tryParseMethod == null)
{
throw new InvalidOperationException(string.Format("Cannot find method {0}.TryParse(string, out {0})", typeof(TBase).FullName));
}
ParameterExpression target = Expression.Parameter(typeof(string), "target");
ParameterExpression defaultValue = Expression.Parameter(typeof(TBase?), "defaultValue");
ParameterExpression result = Expression.Parameter(typeof(TBase), "result");
_parser = Expression.Lambda<Func<string, TBase?, TBase, TBase?>>(
Expression.Condition(
Expression.Call(tryParseMethod, target, result),
Expression.ConvertChecked(result, typeof(TBase?)),
defaultValue),
target, defaultValue, result).Compile();
}
public static TBase? Parse(string target, TBase? defaultValue)
{
return _parser.Invoke(target, defaultValue, default(TBase));
}
}
}
}
pants!
Why can't you use the parsing already available?
int.Parse("1234");
decimal.Parse("1234");
double.Parse("1234");
Or use TryParse if you're not sure it will be able to successfully parse.
Or if you wanted to implement it as an extension method, take a look at this article that will show you how to create a Generic String.Parse method.
Edit: I have no idea how that site went down so quickly after I posted my answer. Here is the class that the article created:
using System;
using System.ComponentModel;
public static class Parser {
public static T Parse<T>(this string value) {
// Get default value for type so if string
// is empty then we can return default value.
T result = default(T);
if (!string.IsNullOrEmpty(value)) {
// we are not going to handle exception here
// if you need SafeParse then you should create
// another method specially for that.
TypeConverter tc = TypeDescriptor.GetConverter(typeof(T));
result = (T)tc.ConvertFrom(value);
}
return result;
}
}
Examples:
// regular parsing
int i = "123".Parse<int>();
int? inull = "123".Parse<int?>();
DateTime d = "01/12/2008".Parse<DateTime>();
DateTime? dn = "01/12/2008".Parse<DateTime?>();
// null values
string sample = null;
int? k = sample.Parse<int?>(); // returns null
int l = sample.Parse<int>(); // returns 0
DateTime dd = sample.Parse<DateTime>(); // returns 01/01/0001
DateTime? ddn = sample.Parse<DateTime?>(); // returns null
I know this question is four years old, but still you could consider using this:
public static T Parse<T>(this string target)
{
Type type = typeof(T);
//In case of a nullable type, use the underlaying type:
var ReturnType = Nullable.GetUnderlyingType(type) ?? type;
try
{
//in case of a nullable type and the input text is null, return the default value (null)
if (ReturnType != type && target == null)
return default(T);
return (T)Convert.ChangeType(target, ReturnType);
}
catch
{
return default(T);
}
}
You can do this with a series of .TryParse() if blocks, but you won't be able to do much with it, since this method will have to return type object. So at the call site, you'll just have to attempt to cast it before doing any arithmetic or anything anyway.
You could write a wrapper function that calls tryParse for each type that you want to support.
There are two problems with that scenario. First you would have to write some code to analyse the string to try to determine what data types it would be possible to parse into, and then some logic to choose one of them. (The string "1" for example would be parsable to byte, sbyte, int, uint, long, ulong, float, double and Decimal. Even worse, the string "4.8.12" would be parsable to several numeric types as well as DateTime in three different ways resulting in totally different values...)
The other problem is that any method capable of doing that would have to return the value boxed in an object, so you would still need wrapper code for each data type just to unbox the value.
Besides, what would you do with a value where you have no control over the type? So, don't try to make this simpler, it only gets a lot more complicated.