How to get value of unknown properties (part solved already with reflection) - c#

I have an existing c# application to modify and need to loop through an object with unknown properties and have half solved the problem with reflection.
I'm trying to populate a dictionary with the property name and the property value. The code is below and I've given a description of what I need in between the ***s
This is an MVC5 project
private Dictionary<string, string> StoreUserDetails ()
{
var userDetails = new Dictionary<string, string>();
foreach (var userItem in UserItems)
{
var theType = userItem.GetType();
var theProperties = theType.GetProperties();
foreach (var property in theProperties)
{
userDetails.Add(property.Name, ***value of userItem property with this property name***);
}
}
return userDetails;
}
Many thanks in advance for your help.

try this
foreach (var property in theProperties)
{
var userItemVal = property.GetValue(userItem, null);
userDetails.Add(property.Name, userItemVal.ToString());
}

What you're looking for is the PropertyInfo.GetValue() method:
https://msdn.microsoft.com/en-us/library/b05d59ty%28v=vs.110%29.aspx
Example
property.GetValue(userItem, null);
Syntax
public virtual Object GetValue(
Object obj,
Object[] index
)
Parameters
obj
Type: System.Object
The object whose property value will be returned.
index
Type: System.Object[]
Optional index values for indexed properties. The indexes of indexed properties are zero-based. This value should be null for non-indexed properties.
Return Value
Type: System.Object
The property value of the specified object.

this is how you can do it. (by the way, your code might error out on "dictionary key not being unique" since the second userItem will try to add the same property name to the dictionary. you might need a List<KeyValuePair<string, string>>)
foreach (var property in theProperties)
{
// gets the value of the property for the instance.
// be careful of null values.
var value = property.GetValue(userItem);
userDetails.Add(property.Name, value == null ? null : value.ToString());
}
and by the way, if you're in MVC context, you could take a reference to System.Web.Routing and use the following snippet.
foreach (var userItem in UserItems)
{
// RVD is provided by routing framework and it gives a dictionary
// of the object property names and values, without us doing
// anything funky.
var userItemDictionary= new RouteValueDictionary(userItem);
}

Related

Return a single object from a list of C# objects checking for matching properties

I have a requirement to create an object 'factory' that works as follows:
Accepts a list of matching object types.
Compares each property value in the list to see if they match.
Returns a new single instance of that object type, with just the matching fields set.
(For simplicity, we can assume all propeties are strings)
For example, from this list of person objects:
Person1 { Name: "Bob", Job: "Policeman", Location: "London" }
Person2 { Name: "John", Job: "Dentist", Location: "Florida" }
Person3 { Name: "Mike", Job: "Dentist", Location: "London" }
Person4 { Name: "Fred", Job: "Doctor", Location: "London" }
If I passed in a list containing person 2 & 3 it would return a new person like so:
Name: "No Match", Job: "Dentist", Location "No Match"
If I passed in person 3 & 4 it would return a new person:
Name: "No Match", Job: "No Match", Location "London"
So far....
Using the answer from this SO question
:
How to check if all list items have the same value and return it, or return an “otherValue” if they don’t?
I can get this LINQ to work for a single known object, but I need it to be generic.
This covers just one specific property but my objects have 30+ properties.
var otherValue="No Match"
var matchingVal= people.First().Job;
return people.All(x=>x.Job== matchingVal) ? matchingVal: otherValue;
I am also aware I can use reflection to get a list of properties in my object. But how to combine all of that into a single 'factory' is beyond by comprehension.
I don't think this is a unique problem but I cannot find a complete solution in any of my searching. Maybe there is already a Nuget package out there that can help me?
All advice gratefully received.
Your input is an enumeration of some specific kind of objects and you have to create an object of the same type where all properties are filled, where all values are the same. This could be done with something like this:
private static T GetCommonProperties<T>(IEnumerable<T> source) where T : new()
{
var first = true;
var common = new T();
var props = typeof(T).GetProperties();
foreach (var item in source)
{
if (first)
{
first = false;
foreach (var prop in props)
{
var value = prop.GetValue(item, null);
prop.SetValue(common, value);
}
}
else
{
foreach (var prop in props)
{
var itemValue = prop.GetValue(item, null);
var commonValue = prop.GetValue(common, null);
if ((dynamic)itemValue != (dynamic)commonValue)
{
prop.SetValue(common, GetDefault(prop.PropertyType));
}
}
}
}
return common;
}
The given method is not really optimal, cause it uses the dynamic trick to solve the comparison problem of boxed values. Also getting the default value for a specific type could probably implemented by this generic approach:
private static object GetDefault(Type t)
{
return typeof(Program)
.GetMethod(nameof(GetDefaultValue), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(t)
.Invoke(null, null);
}
private static T GetDefaultValue<T>()
{
return default;
}
But it would also be possible to provide a switch statement or Dictionary<Type, object> that returns the desired default value if no match available.
Last but not least, another possible performance improvement would be to remove all PropertyInfo entries from the props variable, cause for any next upcoming object, this check is not necessary anymore and the same way, the loop could be early exited, when no more props are available anymore.
A working example can be found here.

How to get value in object type list C#

I have a System.object type list.
I can't use [0] or [1],... to get value because it is object.
I tried GetType() and use many functions, but didn't work.
Is there any way to read it?
I write a function can convert any object, convert means validate or something.
private static IDictionary<string, object> objectConvert(object data)
{
var result = new Dictionary<string, object>();
var attrs = data.GetType().GetProperties();
foreach (var attr in attrs)
{
// if attribute is object do something (use use recursion )...
// if attribute is list do something (use recursion)...
// if not object or list, just add to result
// after all return result
}
}
Attribute is object I work well, but when it is list I tried so hard but can't get any element in list object.
You can cast it. Use as so that it is null if the cast fails and check it:
var list = yourObjectInstance as IList<type_here>();
if (list != null) {
// cast successful.
}
If you know the actual type of your object, simply cast it to that type using standard casting. You can then use indexer on it like ((List<T>)YourObject)[0].

Obtain members' name and value

I have the following method to return a Dictionary<string, string> with the names of all public members (fields and properties) of an object as the dictionary key. I can get the name of the members, but I can't get their values. Could anyone tell me how to achieve this in the method below:
public Dictionary<String, String> ObjectProperty(object objeto)
{
Dictionary<String, String> dictionary = new Dictionary<String, String>();
Type type = objeto.GetType();
FieldInfo[] field = type.GetFields();
PropertyInfo[] myPropertyInfo = type.GetProperties();
String value = null;
foreach (var propertyInfo in myPropertyInfo)
{
value = (string)propertyInfo.GetValue(this, null); //Here is the error
dictionary.Add(propertyInfo.Name.ToString(), value);
}
return dictionary;
}
Error:
Object does not match target type.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Reflection.TargetException: Object does not match target type.
Two things here:
You're passing in this, instead of objeto, which means you're trying to read the properties off of the wrong object.
You're not ensuring that you're only trying to read properties that aren't indexers.
Try changing the foreach to this:
foreach (var propertyInfo in myPropertyInfo)
{
if (propertyInfo.GetIndexParameters().Length == 0)
{
value = (string) propertyInfo.GetValue(objeto, null);
dictionary.Add(propertyInfo.Name.ToString(), value);
}
}
A note, here:
foreach (var propertyInfo in myPropertyInfo)
{
value = (string) propertyInfo.GetValue(this, null); //Here is the error
dictionary.Add(propertyInfo.Name.ToString(), value);
}
You are assuming that ALL your properties are strings. Are they?
If they aren't, but you want strings anyway, you can use this code:
object objValue = propertyInfo.GetValue(objeto, null);
value = (objValue == null) ? null : objValue.ToString();
The above code also takes into consideration that property values may be null. I didn't take into consideration the possibility of indexed properties, but if you have any, you'll need to accommodate them.
Also, as Lasse V. Karlsen has pointed out, by passing this instead of objeto, you are trying to pull property values from the parent class of the method, not objeto. If they aren't the same object, you won't get the results you want; if they aren't even the same type of object, then you'll get an error.
Finally, you've used the term "attributes", which refers to something other than properties in .NET, and also you've referred to class variables, which are also not properties. Are the properties actually what you want, as opposed to "fields" or attributes applied to the definition of the class?

TargetParameterCountException when enumerating through properties of string

I'm using the following code to output values of properties:
string output = String.Empty;
string stringy = "stringy";
int inty = 4;
Foo spong = new Foo() {Name = "spong", NumberOfHams = 8};
foreach (PropertyInfo propertyInfo in stringy.GetType().GetProperties())
{
if (propertyInfo.CanRead) output += propertyInfo.GetValue(stringy, null);
}
If I run this code for the int, or for the Foo complex type, it works fine. But if I run it for the string (as shown), I get the following error on the line inside the foreach loop:
System.Reflection.TargetParameterCountException: Parameter count mismatch.
Does anyone know what this means and how to avoid it?
In case anyone asks 'why are you enumerating through the properties of a string', eventually I hope to create a generic class which will output the properties of any type passed to it (which might be a string...).
In this case, one of the string's properties is the indexer for returning the character at the specified location. Thus, when you try to GetValue, the method expects an index but doesn't receive one, causing the exception.
To check which properties require index you can call GetIndexParameters on the PropertyInfo object. It returns an array of ParameterInfo, but you can just check the length of that array (if there are no parameters, it will be zero)
System.String has an indexed property that returns the char in the specified location. An indexed property needs an argument (index in this case) therefore the exception.
Just as reference... if you want to avoid the TargetParameterCountException when reading properties values:
// Ask each childs to notify also, if any could (if possible)
foreach (PropertyInfo prop in options.GetType().GetProperties())
{
if (prop.CanRead) // Does the property has a "Get" accessor
{
if (prop.GetIndexParameters().Length == 0) // Ensure that the property does not requires any parameter
{
var notify = prop.GetValue(options) as INotifyPropertyChanged;
if (notify != null)
{
notify.PropertyChanged += options.OptionsBasePropertyChanged;
}
}
else
{
// Will get TargetParameterCountException if query:
// var notify = prop.GetValue(options) as INotifyPropertyChanged;
}
}
}
Hope it helps ;-)

Get value of c# dynamic property via string

I'd like to access the value of a dynamic c# property with a string:
dynamic d = new { value1 = "some", value2 = "random", value3 = "value" };
How can I get the value of d.value2 ("random") if I only have "value2" as a string? In javascript, I could do d["value2"] to access the value ("random"), but I'm not sure how to do this with c# and reflection. The closest I've come is this:
d.GetType().GetProperty("value2") ... but I don't know how to get the actual value from that.
As always, thanks for your help!
Once you have your PropertyInfo (from GetProperty), you need to call GetValue and pass in the instance that you want to get the value from. In your case:
d.GetType().GetProperty("value2").GetValue(d, null);
public static object GetProperty(object target, string name)
{
var site = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(Microsoft.CSharp.RuntimeBinder.Binder.GetMember(0, name, target.GetType(), new[]{Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(0,null)}));
return site.Target(site, target);
}
Add reference to Microsoft.CSharp. Works also for dynamic types and private properties and fields.
Edit: While this approach works, there is almost 20× faster method from the Microsoft.VisualBasic.dll assembly:
public static object GetProperty(object target, string name)
{
return Microsoft.VisualBasic.CompilerServices.Versioned.CallByName(target, name, CallType.Get);
}
Dynamitey is an open source .net std library, that let's you call it like the dynamic keyword, but using the a string for the property name rather than the compiler doing it for you, and it ends up being equal to reflection speedwise (which is not nearly as fast as using the dynamic keyword, but this is due to the extra overhead of caching dynamically, where the compiler caches statically).
Dynamic.InvokeGet(d,"value2");
The easiest method for obtaining both a setter and a getter for a property which works for any type including dynamic and ExpandoObject is to use FastMember which also happens to be the fastest method around (it uses Emit).
You can either get a TypeAccessor based on a given type or an ObjectAccessor based of an instance of a given type.
Example:
var staticData = new Test { Id = 1, Name = "France" };
var objAccessor = ObjectAccessor.Create(staticData);
objAccessor["Id"].Should().Be(1);
objAccessor["Name"].Should().Be("France");
var anonymous = new { Id = 2, Name = "Hilton" };
objAccessor = ObjectAccessor.Create(anonymous);
objAccessor["Id"].Should().Be(2);
objAccessor["Name"].Should().Be("Hilton");
dynamic expando = new ExpandoObject();
expando.Id = 3;
expando.Name = "Monica";
objAccessor = ObjectAccessor.Create(expando);
objAccessor["Id"].Should().Be(3);
objAccessor["Name"].Should().Be("Monica");
var typeAccessor = TypeAccessor.Create(staticData.GetType());
typeAccessor[staticData, "Id"].Should().Be(1);
typeAccessor[staticData, "Name"].Should().Be("France");
typeAccessor = TypeAccessor.Create(anonymous.GetType());
typeAccessor[anonymous, "Id"].Should().Be(2);
typeAccessor[anonymous, "Name"].Should().Be("Hilton");
typeAccessor = TypeAccessor.Create(expando.GetType());
((int)typeAccessor[expando, "Id"]).Should().Be(3);
((string)typeAccessor[expando, "Name"]).Should().Be("Monica");
Much of the time when you ask for a dynamic object, you get an ExpandoObject (not in the question's anonymous-but-statically-typed example above, but you mention JavaScript and my chosen JSON parser JsonFx, for one, generates ExpandoObjects).
If your dynamic is in fact an ExpandoObject, you can avoid reflection by casting it to IDictionary, as described at http://msdn.microsoft.com/en-gb/library/system.dynamic.expandoobject.aspx.
Once you've cast to IDictionary, you have access to useful methods like .Item and .ContainsKey
The GetProperty/GetValue does not work for Json data, it always generate a null exception, however, you may try this approach:
Serialize your object using JsonConvert:
var z = Newtonsoft.Json.JsonConvert.DeserializeObject(Convert.ToString(request));
Then access it directly casting it back to string:
var pn = (string)z["DynamicFieldName"];
It may work straight applying the Convert.ToString(request)["DynamicFieldName"], however I haven't tested.
d.GetType().GetProperty("value2")
returns a PropertyInfo object.
So then do
propertyInfo.GetValue(d)
To get properties from dynamic doc
when .GetType() returns null, try this:
var keyValuePairs = ((System.Collections.Generic.IDictionary<string, object>)doc);
var val = keyValuePairs["propertyName"].ToObject<YourModel>;
This is the way i ve got the value of a property value of a dinamic:
public dynamic Post(dynamic value)
{
try
{
if (value != null)
{
var valorCampos = "";
foreach (Newtonsoft.Json.Linq.JProperty item in value)
{
if (item.Name == "valorCampo")//property name
valorCampos = item.Value.ToString();
}
}
}
catch (Exception ex)
{
}
}
Some of the solutions were not working with a valuekind object that I obtained from a json string, maybe because I did not have a concrete type in my code that was similar to the object that I would obtain from the json string, so how I went about it was
JsonElement myObject = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(jsonStringRepresentationOfMyObject);
/*In this case I know that there is a property with
the name Code, otherwise use TryGetProperty. This will
still return a JsonElement*/
JsonElement propertyCode = myObject.GetProperty("Code");
/*Now with the JsonElement that represents the property,
you can use several methods to retrieve the actual value,
in this case I know that the value in the property is a string,
so I use the GetString method on the object. If I knew the value
was a double, then I would use the GetDouble() method on the object*/
string code = propertyCode.GetString();
That worked for me
In .Net core 3.1 you can try like this
d?.value2 , d?.value3
Similar to the accepted answer, you can also try GetField instead of GetProperty.
d.GetType().GetField("value2").GetValue(d);
Depending on how the actual Type was implemented, this may work when GetProperty() doesn't and can even be faster.
In case you have a dynamic variable such as a DapperRow for example you can first build up an ExpandoObject, then cast the Expando into an IDictionary<string, object>. From then on, getting a value via the name of a property is possible.
Helper method ToExpandoObject:
public static ExpandoObject ToExpandoObject(object value)
{
IDictionary<string, object> dapperRowProperties = value as IDictionary<string, object>;
IDictionary<string, object> expando = new ExpandoObject();
if (dapperRowProperties == null)
{
return expando as ExpandoObject;
}
foreach (KeyValuePair<string, object> property in dapperRowProperties)
{
if (!expando.ContainsKey(property.Key))
{
expando.Add(property.Key, property.Value);
}
else
{
//prefix the colliding key with a random guid suffixed
expando.Add(property.Key + Guid.NewGuid().ToString("N"), property.Value);
}
}
return expando as ExpandoObject;
}
Sample usage, I have marked in bold the casting which gives us access in the example, I have marked the important bits with the ** letters:
using (var transactionScope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
foreach (var dynamicParametersForItem in dynamicParametersForItems)
{
var idsAfterInsertion = (await connection.QueryAsync<object>(sql, dynamicParametersForItem)).ToList();
if (idsAfterInsertion != null && idsAfterInsertion.Any())
{
**var idAfterInsertionDict = (IDictionary<string, object>) ToExpandoObject(idsAfterInsertion.First());**
string firstColumnKey = columnKeys.Select(c => c.Key).First();
**object idAfterInsertionValue = idAfterInsertionDict[firstColumnKey];**
addedIds.Add(idAfterInsertionValue); //we do not support compound keys, only items with one key column. Perhaps later versions will return multiple ids per inserted row for compound keys, this must be tested.
}
}
}
In my example, I look up a property value inside a dynamic object DapperRow and first convert the Dapper row into an ExpandoObject and cast it into a dictionary property bag as shown and mentioned in other answers here.
My sample code is the InsertMany method for Dapper extension I am working on, I wanted to grab hold of the multiple ids here after the batch insert.
Use dynamic with Newtonsoft.Json.JsonConvert.DeserializeObject:
// Get JSON string of object
var obj = new { value1 = "some", value2 = "random", value3 = "value" };
var jsonString = JsonConvert.SerializeObject(obj);
// Use dynamic with JsonConvert.DeserializeObject
dynamic d = JsonConvert.DeserializeObject(jsonString);
// output = "some"
Console.WriteLine(d["value1"]);
Sample:
https://dotnetfiddle.net/XGBLU1

Categories

Resources