Get value and name of property of an object - c#

I'm trying to build some objects based on properties coming from another object. The class of the objects I need to build is
public class Data
{
public string Attribute { get; set; }
public string Value{ get; set; }
}
And the attribute will be the name of the property (and the value its value)
So I was trying to use Expressions trees to make a method that I can use for avoiding hard coding that attribute
Up to the moment I came to these couple of methods, based on a couple of posts I was reading on the net
public static string GetName<T>(Expression<Func<T>> e)
{
var member = (MemberExpression)e.Body;
return member.Member.Name;
}
public static Data BuildData<T>(Expression<Func<T>> e, appDetailCategory category)
{
var member = (MemberExpression)e.Body;
Expression strExpr = member.Expression;
var name = member.Member.Name;
var value = Expression.Lambda<Func<string>>(strExpr).Compile()();
return new Data
{
Attribute = name,
Value = value
};
}
But the line I'm trying to set the value raises an exception:
Expression of type 'AutomapperTest.Program+DecisionRequest' cannot be used for return type 'System.String'
I'm pretty sure this message it's supposed to make the error obvious but it's not for me
UPDATE:
I'm calling it this way
private static Data[] GetApplicatonDetailsFromRequest(DecisionRequest request)
{
BuildData(() => request.PubID)
//...
}

Must be member, not member.Expression.
public static Data BuildData<T>(Expression<Func<T>> e, appDetailCategory category)
{
var member = (MemberExpression)e.Body;
var name = member.Member.Name;
var value = Expression.Lambda<Func<string>>(member).Compile()();
return new Data
{
Attribute = name,
Value = value
};
}

It looks like your problem is that the type of PubID isn't a string. You've got two options, either change Data to store the value as an object or call ToString on the value returned from the property and store it. For example:
public static Data BuildData<T>(Expression<Func<T>> e)
{
var member = (MemberExpression)e.Body;
var name = member.Member.Name;
Func<T> getPropertyValue=e.Compile();
object value = getPropertyValue();
return new Data
{
Attribute = name,
Value = value.ToString()
};
}
Note that to get the value you can just compile the Func expression.

Related

Get the field name from the parameter [duplicate]

I want to get the value of a field of an object by using a string as variable name.
I tried to do this with reflection:
myobject.GetType().GetProperty("Propertyname").GetValue(myobject, null);
This works perfectly but now I want to get the value of "sub-properties":
public class TestClass1
{
public string Name { get; set; }
public TestClass2 SubProperty = new TestClass2();
}
public class TestClass2
{
public string Address { get; set; }
}
Here I want to get the value Address from a object of TestClass1.
You already did everything you need to do, you just have to do it twice:
TestClass1 myobject = ...;
// get SubProperty from TestClass1
TestClass2 subproperty = (TestClass2) myobject.GetType()
.GetProperty("SubProperty")
.GetValue(myobject, null);
// get Address from TestClass2
string address = (string) subproperty.GetType()
.GetProperty("Address")
.GetValue(subproperty, null);
Your SubProperty member is actually a Field and not a Property, that is why you can not access it by using the GetProperty(string) method. In your current scenario, you should use the following class to first get the SubProperty field, and then the Address property.
This class will allow you to specify the return type of your property by closing the type T with the appropriate type. Then you will simply need to add to the first parameter the object whose members you are extracting. The second parameter is the name of the field you are extracting while the third parameter is the name of the property whose value you are trying to get.
class SubMember<T>
{
public T Value { get; set; }
public SubMember(object source, string field, string property)
{
var fieldValue = source.GetType()
.GetField(field)
.GetValue(source);
Value = (T)fieldValue.GetType()
.GetProperty(property)
.GetValue(fieldValue, null);
}
}
In order to get the desired value in your context, simply execute the following lines of code.
class Program
{
static void Main()
{
var t1 = new TestClass1();
Console.WriteLine(new SubMember<string>(t1, "SubProperty", "Address").Value);
}
}
This will give you the value contained in the Address property. Just make sure you first add a value to the said property.
But should you actually want to change the field of your class into a property, then you should make the following change to the original SubMember class.
class SubMemberModified<T>
{
public T Value { get; set; }
public SubMemberModified(object source, string property1, string property2)
{
var propertyValue = source.GetType()
.GetProperty(property1)
.GetValue(source, null);
Value = (T)propertyValue.GetType()
.GetProperty(property2)
.GetValue(propertyValue, null);
}
}
This class will now allow you to extract the property from your initial class, and get the value from the second property, which is extracted from the first property.
try
myobject.GetType().GetProperty("SubProperty").GetValue(myobject, null)
.GetType().GetProperty("Address")
.GetValue(myobject.GetType().GetProperty("SubProperty").GetValue(myobject, null), null);

Is there a way to pass a class property as a parameter to a method?

I am trying to find a way to take a class's property and pass it to a method along with another variable to update the property based on conditions. For example
The class
public class MyClass{
public string? Prop1 { get; set; }
public string? Prop2 { get; set; }
public bool? Prop3 { get; set; }
public DateTime? Prop4 { get; set; }
... etc...
}
Test code I would like to get to work...:
var obj = new MyClass();
MyCheckMethod(ref obj.Prop1, someCollection[0,1]);
in the method:
private void MyCheckMethod(ref Object obj, string value)
{
if (!string.isnullorempty(value))
{
// data conversion may be needed here depending on data type of the property
obj = value;
}
}
I want to be able to pass any property of any class and update the property only after validating the value passed in the method. I was hoping I could do this with generics, but I haven't yet found a way to do so. Or if I am over complicating what I need to do.
The problem is that there may be a bit more to the validation of the passed in value than just a simple isnullorempy check.
I also thought about doing something like this:
private void MyCheckMethod(ref object obj, Action action)
Then I could do something like this:
...
MyCheckMethod(ref obj.Prop1, (somecollection[0,1]) => {
... etc....
})
So I am looking for some guidance on how to proceed.
updated info:
The incoming data is all in string format (this is how a 3rd party vendor supplies the data). The data is supplied via API call for the 3rd party product... part of their SDK. However in my class I need to have proper data types. Convert string values to datetime for dates, string values to int for int data types, etc... . The other caveat is that if there isnt a valid value for the data type then the default value of the property should be NULL.
Additional Information:
The incoming data is always in string format.
eg:
I have to update a boolean property.
The incoming value is "". I test to see if the string Value isNullOrEmpty. It is so I dont do anything to property.
The next property datatype is decimal.
The incoming value is "0.343".
I Test to see if the string value is NullorEmpty. It isnt so I can update the property once I do a convert etc.....
Hope this helps.
Thanks
Full solution after edits:
public static class Extensions
{
//create other overloads
public static void MyCheckMethodDate<TObj>(this TObj obj,Expression<Func<TObj,DateTime>> property, string value)
{
obj.MyCheckMethod(property, value, DateTime.Parse);
}
public static void MyCheckMethod<TObj,TProp>(this TObj obj,Expression<Func<TObj,TProp>> property, string value,Func<string, TProp> converter)
{
if(string.IsNullOrEmpty(value))
return;
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if(null != propertyInfo && propertyInfo.CanWrite)
{
propertyInfo.SetValue(obj, converter(value));
}
}
}
public class Obj
{
public object Prop1{get;set;}
public string Prop2{get;set;}
public DateTime Prop3{get;set;}
}
public class Program
{
public static void Main()
{
var obj = new Obj();
obj.MyCheckMethodDate(x=>x.Prop3, "2018-1-1");
Console.WriteLine(obj.Prop3);
}
}
You can pass a lambda expression:
void DoSomething<T>(Expression<Func<T>> property)
{
var propertyInfo = ((MemberExpression)property.Body).Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException("The lambda expression 'property' should point to a valid Property");
}else{
var name = ((MemberExpression)property.Body).Member.Name;
var value = property.Compile();
//Do whatever you need to do
}
}
To use:
DoSomething(() => obj.Property1);
You can't pass a reference to an arbitrary property. A property is basically implemented as two methods, set_Property and get_Property, and there's no way to bundle these together.
One option is to have your checker function take delegates to access the property. For example:
private void MyCheckMethod(Func<string> getter, Action<string> setter)
{
var value = getter();
var newValue = value.ToUpper();
setter(value);
}
So now you would say something like this:
public class MyClass
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
var c = new MyClass();
MyCheckMethod(() => c.Prop1, value => c.Prop1 = value);
Use reflection with compiled expressions.
This performs better than reflection and a little bit slower than native code.
It's not type safe, but you can add runtime validation.

C# Attribute check is an value equals the constructor argument and get constructor values

How can i check that some string is equal to the "constructor" arguments of an attribute?
And how to get all constructor values (TestArg1, TestArg2)?
struct MyData
{
[MyAttr("TestArg1", "TestArg2")] //check that some string equals TestArg1/TestArg2
public string TestArg;
}
This primarily depends on what attribute you're looking at and how it's coded. See the code below as an example on how to do what you're asking.
//The attribute we're looking at
public class MyAtt : System.Attribute
{
public string name;
public string anotherstring;
public MyAtt(string name, string anotherstring)
{
this.name = name;
this.anotherstring = anotherstring;
}
}
public static class Usage
{
[MyAtt("String1", "String2")] //Using the attribute
public static string SomeProperty = "String1";
}
public static class Program
{
public static void Main()
{
Console.WriteLine(IsEqualToAttribute("String1"));
Console.WriteLine(IsEqualToAttribute("blah"));
Console.ReadKey();
}
public static bool IsEqualToAttribute(string mystring)
{
//Let's get all the properties from Usage
PropertyInfo[] props = typeof(Usage).GetProperties();
foreach (var prop in props)
{
//Let's make sure we have the right property
if (prop.Name == "SomeProperty")
{
//Get the attributes from the property
var attrs = prop.GetCustomAttributes();
//Select just the attribute named "MyAtt"
var attr = attrs.SingleOrDefault(x => x.GetType().Name == "MyAtt");
MyAtt myAttribute = attr as MyAtt; //Just casting to the correct type
if (myAttribute.name == mystring) //Compare the strings
return true;
if (myAttribute.anotherstring == mystring) //Compare the strings
return true;
}
}
return false;
}
}
As you can see we get the attribute off the property using reflection and then just compare the properties.
More info can be found here:
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/attributes/accessing-attributes-by-using-reflection
As far as getting the constructor properties something along the lines of
typeof(MyAtt).GetConstructor().GetParameters()
Would retrieve the parameter details for the constructor.
There is also info on this in the Microsoft Docs: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata.constructor?view=netframework-4.7.2
Here is one way to do what you are asking for, but it's not particularly scaleable and requires a bunch of manual code to get working but may get you on the road to what you are trying to achieve. Assuming we have an attribute something like this that takes a string array in it's constructor:
public class MyAttrAttribute : Attribute
{
public string[] AllowedValues { get; }
public MyAttrAttribute(params string[] values)
{
AllowedValues = values;
}
}
You can change your field to be a property with a backing field. This allows you to override the set method and do your checking in there:
private string _testArg;
[MyAttr("TestArg1", "TestArg2")] //check that some string equals TestArg1/TestArg2
public string TestArg
{
get => _testArg;
set
{
var allowedValues = this.GetType() //Get the type of 'this'
.GetProperty(nameof(TestArg)) // Get this property
.GetCustomAttribute<MyAttrAttribute>() // Get the attribute
.AllowedValues; //Get the allowed values specified in the attribute
if(!allowedValues.Contains(value))
{
throw new ArgumentOutOfRangeException(nameof(value),
$"The value '{value}' is not allowed");
}
_testArg = value;
}
}
Having said all of this, I firmly believe that there is a better way to achieve what you are asking. For example, if you are restricted to a minimal set of values, then an enum would almost certainly be a better option than a string.

Dapper and Enums as Strings

I am trying to use Dapper and Dapper-Extensions and to serialize my enums on the database as string.
Right now they are serialized as integers (inside a VARCHAR field) instead.
Is there any way to do this?
Any custom type mapping that I can add?
I might need to move back to EF if i can't pull this through..
There's a way, which I think is more robust and clean.
The solution I provide will work for any enumeration, but it involves some extra coding. It also involves adding a custom type handler in Dapper. However, if this answer gets some votes, I will change the Dapper source code to include this solution automatically in the type handling and ask for a pull request.
I actually implemented this solution and use it in production.
Here goes.
First the struct (not a class, because the struct simply holds a string reference) that will be used as enumeration:
public struct Country
{
string value;
public static Country BE => "BE";
public static Country NL => "NL";
public static Country DE => "DE";
public static Country GB => "GB";
private Country(string value)
{
this.value = value;
}
public static implicit operator Country(string value)
{
return new Country(value);
}
public static implicit operator string(Country country)
{
return country.value;
}
}
Now we need a type handler for this struct
public class CountryHandler : SqlMapper.ITypeHandler
{
public object Parse(Type destinationType, object value)
{
if (destinationType == typeof(Country))
return (Country)((string)value);
else return null;
}
public void SetValue(IDbDataParameter parameter, object value)
{
parameter.DbType = DbType.String;
parameter.Value = (string)((dynamic)value);
}
}
Somewhere in the startup of the application we have to register the type handler with Dapper
Dapper.SqlMapper.AddTypeHandler(typeof(Country), new CountryHandler());
Now you can simply use Country as an "enum". For instance:
public class Address
{
public string Street { get; set; }
public Country Country { get; set; }
}
var addr = new Address { Street = "Sesamestreet", Country = Country.GB };
The downside of course is that the enumeration is not backed in memory by an integer but by a string.
Thanks to Marc Gravell reply:
The only way is to do the inserts manually.
Also using the following post: How do I perform an insert and return inserted identity with Dapper?
Below my solution.
Note that selects work automatically: you can use Dapper (Extensions) directly GetList<T>, there is no mapping to the enum back required.
public enum ComponentType
{
First,
Second,
Third
}
public class Info
{
public int Id { get; set; }
public ComponentType InfoComponentType { get; set; }
public static void SaveList(List<Info> infoList)
{
string ConnectionString = GetConnectionString();
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
foreach (Info info in infoList)
{
string sql = #"INSERT INTO [Info] ([InfoComponentType])
VALUES (#InfoComponentType);
SELECT CAST(SCOPE_IDENTITY() AS INT)";
int id = conn.Query<int>(sql, new
{
InfoComponentType = info.InfoComponentType.ToString()
}).Single();
info.Id = id;
}
conn.Close();
}
}
}
My technique is simliar to neeohw's but lets me use real enums. And it's generic so I don't have to write it many times.
There's an immutable struct that wraps the enum value. It has a single property and implicit conversions, plus a generic custom type handler.
public readonly struct DapperableEnum<TEnum> where TEnum : Enum
{
[JsonConverter(typeof(StringEnumConverter))]
public TEnum Value { get; }
static DapperableEnum()
{
Dapper.SqlMapper.AddTypeHandler(typeof(DapperableEnum<TEnum>), new DapperableEnumHandler<TEnum>());
}
public DapperableEnum(TEnum value)
{
Value = value;
}
public DapperableEnum(string description)
{
Value = EnumExtensions.GetValueByDescription<TEnum>(description);
}
public static implicit operator DapperableEnum<TEnum>(TEnum v) => new DapperableEnum<TEnum>(v);
public static implicit operator TEnum(DapperableEnum<TEnum> v) => v.Value;
public static implicit operator DapperableEnum<TEnum>(string s) => new DapperableEnum<TEnum>(s);
}
public class DapperableEnumHandler<TEnum> : SqlMapper.ITypeHandler
where TEnum : Enum
{
public object Parse(Type destinationType, object value)
{
if (destinationType == typeof(DapperableEnum<TEnum>))
{
return new DapperableEnum<TEnum>((string)value);
}
throw new InvalidCastException($"Can't parse string value {value} into enum type {typeof(TEnum).Name}");
}
public void SetValue(IDbDataParameter parameter, object value)
{
parameter.DbType = DbType.String;
parameter.Value =((DapperableEnum<TEnum>)value).Value.GetDescription();
}
}
I use the static constructor to automatically register the type handler at startup.
I use GetDescription / GetValueByDescription (same idea as this answer) to support strings that wouldn't be valid C# enum values. If you don't need this feature, ToString and Enum.Parse will work fine.
The JsonConverter attribute makes Json.Net use string values too. Of course remove it if you don't use Json.Net
Here's an example:
enum Holiday
{
Thanksgiving,
Christmas,
[Description("Martin Luther King, Jr.'s Birthday")]
MlkDay,
Other,
}
class HolidayScheduleItem : IStandardDaoEntity<HolidayScheduleItem>
{
public DapperableEnum<Holiday> Holiday {get; set;}
public DateTime When {get; set;}
}
And calling code can use the normal enum values.
var item = new HolidayScheduleItem()
{
Holiday = Holiday.MlkDay,
When = new DateTime(2021, 1, 18)
};
It works with plain Dapper or Dapper.Contrib:
await conn.ExecuteAsync("INSERT HolidayScheduleItem ([Holiday], [When])
VALUES(#Holiday, #When)", item);
await conn.InsertAsync(item);
I couldn't get the ITypeHandler suggestions to work with enums. However, I was profiling the SQL generated by Dapper and noticed it was declaring the enum parameters as int. So I tried adding a type map for the enum type.
Adding this Dapper config on application startup did the trick for me.
Dapper.SqlMapper.AddTypeMap(typeof(MyEnum), DbType.String);
Then I use connection.Execute(updateSql, model) as normal. Didn't need to use .ToString() or any other explicit conversions. The underlying columns are varchar(20).
Instead of passing in your data object you can pass in a dictionary built off your object w/ the enum converted into a string in the dictionary (so Dapper never sees the Enum)
iow instead of say
connection.Query<MyDataObjectType>(sql, myDataObject);
you can do
connection.Query<MyDataObjectType>(sql, myDataObject.AsDapperParams());
and then have a method like
public static Dictionary<string, object> AsDapperParams(this object o)
{
var properties = o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(c => c.CanRead).ToArray();
return properties
.Select(c => new {Key = c.Name, Value = c.GetValue(o), Type = c.PropertyType})
.ToDictionary(
c => c.Key,
c => (c.Type.IsEnum || Nullable.GetUnderlyingType(c.Type)
?.IsEnum == true) ? c.Value.ToString() : c.Value);
}
I find this approach works well with DapperExtensions. Setup a Enum field as per normal in your class but then have a 2nd string field for the Enum which represents the value you will be persisting. You can then set the mappings to ignore the Enum field but persist the string value instead. e.g.
// enum field
public Frequency Frequency { get; set;}
// string field of the same enum that you are going to persist
public string DbFrequency
{
get { return this.Frequency.ToString(); }
set { this.Frequency = Enum.Parse<Frequency>(value); }
}
// in your mappings do this
Map(f => f.Frequency).Ignore();
Map(f => f.DbFrequency).Column("Frequency");
It would be to have 2nd string enum as a private member of the class but you have to make it public for this to work AFAIK

value of that propertyObject does not match target type in c# (get property value with reflection)

I have a class that contain this property.
public List<string> Messages { get; set; }
I want to read value of that property with using reflection.
List<string> messages = new List<string>();
PropertyInfo prop = myType.GetProperty("Messages");
var message = prop.GetValue(messages);
but I get this error :
"Object does not match target type."
I used this line:
var message = prop.GetValue(messages,null);
instead of
var message = prop.GetValue(messages);
but still I get the same error.
PropertyInfo contains the metadata about your Messages property. You can use that PropertyInfo to get or set the value of that property on some instance of that type. That means that you need to pass an instance of your type, from which you want to read the property Messages, into the GetValue call:
messages = (List<string>)prop.GetValue(instanceOfMyType);
Here is a working example of what you are trying to do:
class A
{
public List<string> Messages { get; set; }
public static void Test()
{
A obj = new A { Messages = new List<string> { "message1", "message2" } };
PropertyInfo prop = typeof(A).GetProperty("Messages");
List<string> messages = (List<string>)prop.GetValue(obj);
}
}
Just for the record, this implementation makes no sense in real life, since you can get the value directly through obj.Messages

Categories

Resources