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
Related
I am using Dapper ( and I couldn't be happier), I know how to access normal stored procedures as mentioned here, however, how do I pass on the the Npgsql refcursor name to the proc (using C#)? For example:
I have a proc that looks like:
FUNCTION xx.getData(
v_ref refcursor,
v_id integer)
RETURNS refcursor AS
...
How would I specify the parameters for xx.getData?
For example, if getData accepted just one parameter of type int, then I could call it like so:
var data = cnn.Query<myType>("xx.getData", new {Id = 1},
commandType: CommandType.StoredProcedure);
OR
var p = new DynamicParameters();
p.Add("#id", 11);
cnn.Execute("xx.getData", p, commandType: CommandType.StoredProcedure);
I can't find the correct type in System.DbType to pass on in the query.
Note that a refcursor corresponds to an active cursor that has already been opened in a previous call. In other words, it does not correspond to a stored procedure, but rather to a resultset (possibly but not necessarily returned from a stored procedure).
Just in case you really do need to send a refcursor, what you're looking for is NpgsqlDbType.Refcursor.
Bellow custom dynamic parameter compatible for Npgsql data types. It will work for with output refcursor parameters.
/// <summary>
/// Npgsql Dynamic Param for Dapper
/// </summary>
public class PgParam : SqlMapper.IDynamicParameters
{
private static readonly Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
private readonly Dictionary<string, ParamInfo> _parameters = new Dictionary<string, ParamInfo>();
private List<object> templates;
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
public PgParam()
{
}
/// <summary>
/// construct a dynamic parameter bag
/// </summary>
/// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
public PgParam(object template)
{
AddDynamicParams(template);
}
/// <summary>
/// All the names of the param in the bag, use Get to yank them out
/// </summary>
public IEnumerable<string> ParameterNames
{
get { return _parameters.Select(p => p.Key); }
}
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
AddParameters(command, identity);
}
/// <summary>
/// Append a whole object full of params to the dynamic
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
/// </summary>
/// <param name="param"></param>
public void AddDynamicParams(
dynamic param
)
{
if (param is object obj)
{
if (!(obj is PgParam subDynamic))
{
if (!(obj is IEnumerable<KeyValuePair<string, object>> dictionary))
{
templates = templates ?? new List<object>();
templates.Add(obj);
}
else
{
foreach (var kvp in dictionary)
{
Add(kvp.Key, kvp.Value);
}
}
}
else
{
if (subDynamic._parameters != null)
foreach (var kvp in subDynamic._parameters)
_parameters.Add(kvp.Key, kvp.Value);
if (subDynamic.templates != null)
{
templates = templates ?? new List<object>();
foreach (var t in subDynamic.templates) templates.Add(t);
}
}
}
}
/// <summary>
/// Add a parameter to this dynamic parameter list
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
/// <param name="dbType"></param>
/// <param name="direction"></param>
/// <param name="size"></param>
public void Add(string name, object value = null, NpgsqlDbType? dbType = null, ParameterDirection? direction = null,int? size = null)
{
_parameters[name] = new ParamInfo
{
Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType,
Size = size
};
}
/// <summary>
/// Add all the parameters needed to the command just before it executes
/// </summary>
/// <param name="command">The raw command prior to execution</param>
/// <param name="identity">Information about the query</param>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
if (templates != null)
foreach (var template in templates)
{
var newIdent = identity.ForDynamicParameters(template.GetType());
Action<IDbCommand, object> appender;
lock (paramReaderCache)
{
if (!paramReaderCache.TryGetValue(newIdent, out appender))
{
appender = SqlMapper.CreateParamInfoGenerator(newIdent, false, true);
paramReaderCache[newIdent] = appender;
}
}
appender(command, template);
}
foreach (var param in _parameters.Values)
{
var add = !((NpgsqlCommand) command).Parameters.Contains(param.Name);
NpgsqlParameter p;
if (add)
{
p = ((NpgsqlCommand) command).CreateParameter();
p.ParameterName = param.Name;
}
else
{
p = ((NpgsqlCommand) command).Parameters[param.Name];
}
var val = param.Value;
p.Value = val ?? DBNull.Value;
p.Direction = param.ParameterDirection;
if (param.Size != null) p.Size = param.Size.Value;
if (param.DbType != null) p.NpgsqlDbType = param.DbType.Value;
if (add) command.Parameters.Add(p);
param.AttachedParam = p;
}
}
/// <summary>
/// Get the value of a parameter
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="name"></param>
/// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
public T Get<T>(string name)
{
var val = _parameters[name].AttachedParam.Value;
if (val == DBNull.Value)
{
if (default(T) != null)
throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
return default(T);
}
return (T) val;
}
private class ParamInfo
{
public string Name { get; set; }
public object Value { get; set; }
public ParameterDirection ParameterDirection { get; set; }
public NpgsqlDbType? DbType { get; set; }
public int? Size { get; set; }
public IDbDataParameter AttachedParam { get; set; }
}
}
I'm using Automapper and three-layered architecture in my application. I have a method which takes Linq query (LambdaExpression) as a parameter.
To pass this parameter in another layer I need to remap it. So, I created an ExpressionExtension class, which is responsible for returning remapped Expression:
/// <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);
}
}
And code for AutoMapVisitor:
/// <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)
{
_newParameter = newParameter;
}
/// <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();
// 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;
var destinationMember = destinationProperty.MemberInfo;
// 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);
}
/// <summary>
///
/// </summary>
/// <typeparam name="T">Func of type TIn and result is TResult</typeparam>
/// <param name="node">Expression of Func with certain input and result types</param>
/// <returns></returns>
protected override Expression VisitLambda<T>(Expression<T> node)
{
//predicate.RemapForType<PersonViewModel, PersonDTO, bool>()
// считаем, что будет 1 параметр(объект) операции
var source = node.Parameters.FirstOrDefault();
// берем первый найденный маппинг источника
TypeMap typeMap = source == null
? null
: Mapper.GetAllTypeMaps().FirstOrDefault(tMap => tMap.SourceType == source.Type);
if (typeMap!=null)
{
var ts = typeMap.SourceType;
var td = typeMap.DestinationType;
var res = node.ReturnType;
return VisitLambda(node, ts, td, res);
//return node.RemapForType<ts,td,res>();
}
return base.VisitLambda<T>(node);
}
private Expression<Func<TOut,TResult>> VisitLambda<T,TIn, TOut, TResult>(Expression<T> node, TIn source, TOut destination, TResult result)
{
ParameterExpression pe = Expression.Parameter(typeof (TIn), "innerParam");
var value = typeof(T) as Func<TIn, TResult>;
Expression<Func<TIn, TResult>> input = Expression.Lambda<Func<TIn, TResult>>(Expression.Invoke(node.Body,node.Parameters)); // tried also without Expression.Invoke
return input.RemapForType<TIn, TOut, TResult>();
}
}
I understand that I need to override base Lambda Visitor and to call ExpressionExtension.RemapForType method once again to remap inner lambda. But how can I pass the right parameters to it? So, I decided to create generic method but nothing works right.
Could you help me to get the right solution.
E.g. something like that:
Expression<Func<TSource,TResult>> a = val => val.FullName == "ABC" && val.SomeEnumerableProperty.Any(inner => inner.City == "Moscow");
// Here we will get remapped lambda with TDestination class as a parameter and TDestination class properties remapped by AutoMapper as you saw in my posted code
Expression<Func<TDestination, TResult>> b = a.RemapForType<TSource,TDestination,TResult>();
And in I don't know which classes will be used as Source and Destination in inner Lambda because it is runtime. So the example is simply for you to understand what I want to do.
EDITED
So, I changed my Visitor in this variant:
/// <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 TypeMap _typeMap;
private readonly List<TypeMap> _typeMaps = new List<TypeMap>();
private readonly Dictionary<Type, ParameterExpression> parameterMap = new Dictionary<Type, ParameterExpression>();
private Dictionary<MemberInfo, Expression> memberMap = new Dictionary<MemberInfo, Expression>();
/// <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)
{
_typeMap = Mapper.FindTypeMapFor<TSource, TDestination>();
Contract.Assume(_typeMap != null);
_typeMaps.Add(_typeMap);
_typeMaps.AddRange(FillNestedTypeMaps(_typeMap));
parameterMap.Add(newParameter.Type, newParameter); // main parameter which we don't need to recreate
foreach (TypeMap map in _typeMaps)
{
if (parameterMap.ContainsKey(map.DestinationType)) continue;
parameterMap.Add(map.DestinationType, Expression.Parameter(map.DestinationType, map.DestinationType.Name.ToLower()));
}
}
/// <summary>
/// находим все возможные маппинги текущей конфигурации вниз по иерархии свойств класса источника
/// </summary>
/// <param name="tMap">Объект конфигурации, содержащий информацию о типе источника и типе назначения</param>
private IEnumerable<TypeMap> FillNestedTypeMaps(TypeMap tMap)
{
List<TypeMap> result = new List<TypeMap>();
// 1 where: маппинг только между классами
// 2 where: маппинг не равен входящему и ReverseMap значению, например на входе: A -> B, тогда отпадут значения A -> B и B -> A из проверки
// 3 where: берем те свойства, тип которых совпадает с входящим типом источника или, если это обобщенный тип, с его аргументом и тоже самое для типа назначения
IEnumerable<PropertyMap> pMaps = tMap.GetPropertyMaps();
var list = Mapper.GetAllTypeMaps()
.Where(
map => map.SourceType.IsClass && map.DestinationType.IsClass)
.Where(map =>
!(map.Equals(tMap) ||
(map.SourceType == tMap.DestinationType && map.DestinationType == tMap.SourceType)))
.Where(
map =>
pMaps
.Any(
pi =>
{
var pis = pi.SourceMember as PropertyInfo;
if (pis == null) return false;
bool forSource = pis.PropertyType == map.SourceType ||
(pis.PropertyType.IsGenericType &&
pis.PropertyType.GetGenericArguments()[0] == map.SourceType);
bool forDestination = pi.DestinationPropertyType == map.DestinationType ||
(pi.DestinationPropertyType.IsGenericType &&
pi.DestinationPropertyType.GetGenericArguments()[0] == map.DestinationType);
return forSource && forDestination;
}))
.ToList();
if (list.Count > 0)
{
result.AddRange(list);
foreach (TypeMap typeMap in list)
{
result.AddRange(FillNestedTypeMaps(typeMap));
}
}
return result;
}
private Type Map(Type type)
{
var tMap = _typeMaps.FirstOrDefault(map => map.SourceType == type);
Contract.Assume(tMap != null);
return tMap.DestinationType;
}
private ParameterExpression Map(ParameterExpression parameter)
{
var mappedType = Map(parameter.Type);
ParameterExpression mappedParameter;
if (!parameterMap.TryGetValue(mappedType, out mappedParameter))
parameterMap.Add(mappedType, mappedParameter = Expression.Parameter(mappedType, parameter.Name));
return mappedParameter;
}
private Expression Map(MemberInfo mi, Expression exp)
{
Expression val;
if (!memberMap.TryGetValue(mi, out val))
{
foreach (PropertyMap propertyMap in
_typeMaps.Select(map => map.GetPropertyMaps().SingleOrDefault(m => m.SourceMember == mi))
.Where(propertyMap => propertyMap != null))
{
memberMap.Add(mi, val = Expression.PropertyOrField(exp, propertyMap.DestinationProperty.MemberInfo.Name));
break;
}
}
return val;
}
/// <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 expression = Visit(node.Expression);
if (expression == node.Expression)
return node;
return Map(node.Member, expression);
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return Expression.Lambda(Visit(node.Body), node.Parameters.Select(Map));
}
protected override Expression VisitParameter(ParameterExpression node)
{
return Map(node);
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
// if static object and generic method
if (node.Object == null && node.Method.IsGenericMethod)
{
// Static generic method
var args = Visit(node.Arguments);
var genericArgs = node.Method.GetGenericArguments().Select(Map).ToArray();
var method = node.Method.GetGenericMethodDefinition().MakeGenericMethod(genericArgs);
return Expression.Call(method, args);
}
return base.VisitMethodCall(node);
}
}
And now after remapping it throws an Exception:
variable 'person' of type 'Reestr.DAL.Entities.Person' referenced from scope '', but it is not defined
It is interesting that this Exception is thrown only when we have an inner Lambda (e.g. method Any). But if we didn't include it, using only simple search conditions, everything works fine! Seems like there is missing reference on Enumerable property of mapped class or there is newly created parameter somewhere and I can't understand where it can be.
Could you give me any suggestions, please, how can I solve this issue! Thanks!
EDITED
As #MBoros mentioned Automapper supports such mapping out of the box (of course if there are no hard code sections). The problem is in ServiceStack.OrmLite!!! I've tried simply passing manually created query with inner subquery and it failed with an exception I've posted earlier...
I want to get the StringLength attribute.
Class code :
var type1 = Type.GetType("MvcApplication4.Models.Sample.SampleMasterModel");
var metadata = ModelMetadataProviders.Current.GetMetadataForType(null, type1);
var properties = metadata.Properties;
var prop = properties.FirstOrDefault(p => p.PropertyName == "Remark");
?? Get StringLength attr?
Model Code:
public class SampleModel
{
[StringLength(50)]
public string Remark { get; set; }
}
Based on wudzik and Habib help. I modified the code.
Final Code:
PropertyInfo propertyInfo = type1.GetProperties().FirstOrDefault(p => p.Name == "Remark");
if (propertyInfo != null)
{
var attributes = propertyInfo.GetCustomAttributes(true);
var stringLengthAttrs =
propertyInfo.GetCustomAttributes(typeof (StringLengthAttribute), true).First();
var stringLength = stringLengthAttrs != null ? ((StringLengthAttribute)stringLengthAttrs).MaximumLength : 0;
}
You can get CustomAttributes through PropertyInfo like:
PropertyInfo propertyInfo = type1.GetProperties().FirstOrDefault(p=> p.Name == "Remark");
if (propertyInfo != null)
{
var attributes = propertyInfo.GetCustomAttributes(true);
}
/// <summary>
/// Returns the StringLengthAttribute for a property based on the property name passed in.
/// Use this method in the class or in a base class
/// </summary>
/// <param name="type">This type of the class where you need the property StringLengthAttribute.</param>
/// <param name="propertyName">This is the property name.</param>
/// <returns>
/// StringLengthAttribute of the propertyName passed in, for the Type passed in
/// </returns>
public static StringLengthAttribute GetStringLengthAttribute(Type type, string propertyName)
{
StringLengthAttribute output = null;
try
{
output = (StringLengthAttribute)type.GetProperty(propertyName).GetCustomAttribute(typeof(StringLengthAttribute));
}
catch (Exception ex)
{
//error handling
}
return output;
} //GetStringLengthAttribute
/// <summary>
/// Returns the StringLengthAttribute for a property based on the property name passed in.
/// Use this method in the class or in a base class
/// </summary>
/// <param name="propertyName">This is the property name.</param>
/// <returns>
/// StringLengthAttribute of the propertyName passed in, for the current class
/// </returns>
public StringLengthAttribute GetStringLengthAttribute(string propertyName)
{
StringLengthAttribute output = null;
try
{
output = (StringLengthAttribute)this.GetType().GetProperty(propertyName).GetCustomAttribute(typeof(StringLengthAttribute));
}
catch (Exception ex)
{
//error handling
}
return output;
} //GetStringLengthAttribute
}
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 am implementing Key Navigation for an application and I want to override the space key functionality when a Combo Box is focused such that it acts like an enter key; like this:
if (!cb.IsDropDownOpen)
{
cb.IsDropDownOpen = true;
}
else
{
cb.SelectedItem = cb.{non-public member HighlightedItem};
cb.IsDropDownOpen = false;
}
The problem is that I need to get the value of that non-public member so that I can set the selected value and close the drop-down (how enter would normally work).
Now the question is: What is the fastest and hassle free way of achieving this?
This is a helper class I have for doing just this:
public static class PropertyHelper
{
/// <summary>
/// Returns a _private_ Property Value from a given Object. Uses Reflection.
/// Throws a ArgumentOutOfRangeException if the Property is not found.
/// </summary>
/// <typeparam name="T">Type of the Property</typeparam>
/// <param name="obj">Object from where the Property Value is returned</param>
/// <param name="propName">Propertyname as string.</param>
/// <returns>PropertyValue</returns>
public static T GetPrivatePropertyValue<T>(this object obj, string propName)
{
if (obj == null) throw new ArgumentNullException("obj");
PropertyInfo pi = obj.GetType().GetProperty(propName,
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance);
if (pi == null)
throw new ArgumentOutOfRangeException("propName",
string.Format("Property {0} was not found in Type {1}", propName,
obj.GetType().FullName));
return (T) pi.GetValue(obj, null);
}
/// <summary>
/// Returns a private Field Value from a given Object. Uses Reflection.
/// Throws a ArgumentOutOfRangeException if the Property is not found.
/// </summary>
/// <typeparam name="T">Type of the Field</typeparam>
/// <param name="obj">Object from where the Field Value is returned</param>
/// <param name="propName">Field Name as string.</param>
/// <returns>FieldValue</returns>
public static T GetPrivateFieldValue<T>(this object obj, string propName)
{
if (obj == null) throw new ArgumentNullException("obj");
Type t = obj.GetType();
FieldInfo fi = null;
while (fi == null && t != null)
{
fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
t = t.BaseType;
}
if (fi == null)
throw new ArgumentOutOfRangeException("propName",
string.Format("Field {0} was not found in Type {1}", propName,
obj.GetType().FullName));
return (T) fi.GetValue(obj);
}
/// <summary>
/// Sets a _private_ Property Value from a given Object. Uses Reflection.
/// Throws a ArgumentOutOfRangeException if the Property is not found.
/// </summary>
/// <typeparam name="T">Type of the Property</typeparam>
/// <param name="obj">Object from where the Property Value is set</param>
/// <param name="propName">Propertyname as string.</param>
/// <param name="val">Value to set.</param>
/// <returns>PropertyValue</returns>
public static void SetPrivatePropertyValue<T>(this object obj, string propName, T val)
{
Type t = obj.GetType();
if (t.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null)
throw new ArgumentOutOfRangeException("propName",
string.Format("Property {0} was not found in Type {1}", propName,
obj.GetType().FullName));
t.InvokeMember(propName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty |
BindingFlags.Instance, null, obj, new object[] {val});
}
/// <summary>
/// Set a private Field Value on a given Object. Uses Reflection.
/// </summary>
/// <typeparam name="T">Type of the Field</typeparam>
/// <param name="obj">Object from where the Property Value is returned</param>
/// <param name="propName">Field name as string.</param>
/// <param name="val">the value to set</param>
/// <exception cref="ArgumentOutOfRangeException">if the Property is not found</exception>
public static void SetPrivateFieldValue<T>(this object obj, string propName, T val)
{
if (obj == null) throw new ArgumentNullException("obj");
Type t = obj.GetType();
FieldInfo fi = null;
while (fi == null && t != null)
{
fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
t = t.BaseType;
}
if (fi == null)
throw new ArgumentOutOfRangeException("propName",
string.Format("Field {0} was not found in Type {1}", propName,
obj.GetType().FullName));
fi.SetValue(obj, val);
}
}
You'd have to use reflection to get the value of the property
PropertyInfo highlightedItemProperty = cb.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Instance).Single(pi => pi.Name == "HighlightedItem");
object highlightedItemValue = highlightedItemProperty.GetValue(cb, null);
To browse all properties or fields, also check out
var allProps = cb.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).OrderBy(pi => pi.Name).ToList();
var allFields = cb.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).OrderBy(pi => pi.Name).ToList();
(you can read through them all in the debugger)