Accessing element in Array, Dictionary or List using Reflection - c#

Ive created a tool for my team which allows them to view an object visually by using the Dump() method of LinqPad. This is purely for use by the development teams to make things easier rather than having to attach a debugger which isnt always possible.
They enter the name of the object they want to Dump and the program does it in its current context.
eg. User enters Sales.CurrentUser and the application visualises the CurrentUser object using LinqPad's Dump() method.
public void VisualiseObject()
{
object CurrentObject = this;
string Result = GetObjectFromUser("Enter Propery or Field path eg. Sales.CurrentUser", "");
if (Result == null)
return;
else if (Result != string.Empty)
{
string[] Objects = Result.Split(".".ToCharArray());
try
{
foreach (string objName in Objects)
{
CurrentObject = GetPropValue(CurrentObject, objName);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
if (CurrentObject == null)
{
MessageBox.Show("Object is null");
return;
}
}
CurrentObject.Dump(Result);
}
public static object GetPropValue(object src, string propName)
{
object result = null;
try
{
result = src.GetType().GetProperty(propName).GetValue(src, null);
}
catch
{
try
{
result = src.GetType().GetField(propName).GetValue(src);
}
catch (Exception)
{
result = null;
}
}
return result;
}
Anyway im trying to think of a way to support the user supplying an index or key to a array, list or dictionary object and handle this in reflection.
The user could supply for example. An array or list Sales.AllUsers[0] or for dictionary Sales.Items["Water"]
Any ideas?

You can try the Dynamic Linq Expressions, it allows you to do parse expressions (just be carefull with code injection).
Or you can try accessing the Get method of the array:
(given that prop is a variable of type int[])
var getter = array.GetType().GetMethod("Get");
var value = getter.Invoke(array, new object[] { index });
Haven't tested with Dictionary or Collection but should also work in the same way.

Related

Code works in Watch Window, but unable to cast object during code execution

I have these lines of code in my .NET 4.7.2 server application:
object saveObject = proxyDef.GetEntityAsNativeObject(entity, DynamicProxyAssembly); // this works
((AxdSalesOrder)saveObject).SalesTable[0].TableDlvAddr = null; // this throws error
I can execute the null-set (2nd line above) in the VS2019 Watch Window and it works perfectly and achieves the desired effect. But when I allow it to run in normal execution (whether in debug mode or not), I get an unhandled exception on that 2nd line:
Unable to cast object of type 'AxdSalesOrder' to type
'Elogix.MSAx.Core.Ax2012ElogixServices.AxdSalesOrder'
There is dynamic stuff going in in relation to that type:
public override object GetEntityAsNativeObject(MSAxEntity entity, Assembly dynamicProxyAssembly) {
var salesOrderObject = Activator.CreateInstance(dynamicProxyAssembly.GetType("AxdSalesOrder"));
var salesOrderTable = DynamicEntityUtil.CreateObjectFromDynamicEntity(entity, dynamicProxyAssembly, "AxdEntity_SalesTable");
Array tableLines =
Array.CreateInstance(
salesOrderObject.GetType().GetProperty("SalesTable").PropertyType.GetElementType(), 1);
tableLines.SetValue(salesOrderTable, 0);
salesOrderObject.SetPropertyValue("SalesTable", tableLines);
return salesOrderObject;
}
public static object CreateObjectFromDynamicEntity(DynamicEntity entity, Assembly dynamicProxyAssembly, string objectTypeName) {
return CreateObjectFromDynamicEntity(entity, dynamicProxyAssembly.GetType(objectTypeName));
}
public static object CreateObjectFromDynamicEntity(DynamicEntity entity, Type type) {
if (type == null) {
throw new ArgumentException("Cannot create object from dynamic entity because \"Type\" is null.");
}
if (type.IsArray) {
return CreateArrayFromDynamicEntity(entity, type);
}
return CreateClassFromDynamicEntity(entity, type);
}
private static object CreateClassFromDynamicEntity(DynamicEntity entity, Type type) {
var nativeObject = Activator.CreateInstance(type);
// this will recursively convert the dynamic values to the native type values on the object.
updateValuesFromDynamicValues(entity);
var modifiedProperties = from property in entity.Properties
//where property.State != DynamicPropertyState.Unchanged
select property;
foreach(var property in modifiedProperties) {
Type valueUnderlyingType = Nullable.GetUnderlyingType(property.Type);
if (valueUnderlyingType != null && valueUnderlyingType.IsEnum) {
PropertyInfo info = nativeObject.GetType().GetProperty(property.Name);
Type targetUnderlyingType = Nullable.GetUnderlyingType(info.PropertyType);
if (property.Value == null) {
info.SetValue(nativeObject, null, null);
} else {
object correctedValue = property.Value.CorrectedEnumValue(targetUnderlyingType);
info.SetValue(nativeObject, correctedValue, null);
}
} else if (property.Type.IsEnum) {
if (property.Value == null) {
continue;
}
object correctedValue = property.Value.CorrectedEnumValue(property.Type);
nativeObject.SetPropertyValue(property.Name, correctedValue);
} else {
try {
nativeObject.SetPropertyValue(property.Name, property.Value);
} catch (Exception ex) {
Log.Write(ex.Message);
}
}
}
return nativeObject;
}
Here is how it looks in the VS2019 Watch Window:
Did this in Immediate Window:
var t = saveObject.GetType();
t.FullName
"AxdSalesOrder"
As you can see, the type's FullName is not very full, not qualified by anything, due to the dynamic nature of the type.
I can try it this way:
(saveObject as AxdSalesOrder).SalesTable[0].TableDlvAddr = null;
Again, that works in Watch, but throws this exception when run in normal execution:
Object reference not set to an instance of an object.
Clearly, VS/Watch knows the type, which is why it can execute without errors inside Watch. But the .net runtime apparently doesn't know the type at run time. How can I get this object cast to work in normal code execution like it does when run in Watch?
The answer here is to take advantage of dynamic typing since the static type is not available here:
dynamic saveObject = proxyDef.GetEntityAsNativeObject(entity, DynamicProxyAssembly);
saveObject.SalesTable[0].TableDlvAddr = null;
Another possible approach would be to use reflection, however since the expression applied to the object involves two properties and an index lookup (.SalesTable[0].TableDlvAddr) this would look much less readable.
The GetEntityAsNativeObject could also benefit from this style, you could consider rewriting it to using dynamic binding rather than reflection.

How do I await this callback method to perform logic on the return data?

So I'm working in Silverlight right now unfortunately for the first time. I'm decently familiar with callbacks, but I'm not entirely sure how to convert this method to be synchronous to perform logic on the order data.
I've been frequently told that making this synchronous was ill-advised, but my goal is to check if certain fields have been modified in the XAML UI and are different from what exists in the database. Then prompt for a reason for the change. If there is a better way to go about this, I'd love to know.
I'm in Silverlight 5 with .Net Framework 4.0 in VS 2013
Thank you! Here's the async order provider:
public void GetOrder(string ordNumber, Action<Func<OrderLoadResults>> callback)
{
String exStr = String.Format("{0}.{1}() --> received an empty value for",
this.GetType().Name,
MethodBase.GetCurrentMethod().Name);
if (ordNumber == null)
{
throw new ArgumentNullException("ordNumber", exStr);
}
if (callback == null)
{
throw new ArgumentNullException("callback", exStr);
}
IOrderServiceAsync channel = CreateChannel();
AsyncCallback asyncCallback = ar => GetOrderCallback(callback, ar);
channel.BeginGetOrderByOrdNumber(ordNumber, asyncCallback.ThreadSafe(), channel);
}
And here's what I'm doing with it:
public List<ATMModifiedFieldModel> CheckForATMModifiedFields()
{
if (!_order.Stops.Items.Any(x => x.ModelState == ModelState.Modified))
{
return null;
}
List<StopModel> oldStop = new List<StopModel>();
Provider.OrderProvider orderProvider = new Provider.OrderProvider();
//Looking to convert this method to sync to pull the order info out to compare against
//orderProvider.GetOrder(_order.Item.OrdHdrNumber.ToString(),getResult => OnGetOrderComplete(getResult));
List<ATMModifiedFieldModel> modifiedFields = new List<ATMModifiedFieldModel>();
foreach (StopModel stop in _order.Stops.Items)
{
if (stop.ModelState == ModelState.Modified)
{
foreach (string ATMFieldName in Enum.GetNames(typeof(ATMFields)))
{
string beforeValue = "before value"; //Should check the value in the database
string afterValue = stop.GetType().GetProperty(ATMFieldName).GetValue(stop, null).ToString();
if (beforeValue != afterValue)
{
modifiedFields.Add(new ATMModifiedFieldModel(ATMFieldName, beforeValue, afterValue, stop.StpNumber, "Stop"));
}
}
}
}
return modifiedFields;
}

Is there a way to use a parameter to set a variable name?

I don't really know how to explain this, but is there a way to place the name of a variable using a string? i have some code to explain my question:
I have my User Class with a username, id, and a lot of other stuff.
public User User;
and I have function that download a json with all that information
public IEnumerator getDataArray(string function, string value, targetObject)
{
queuedDownloads++;
Dictionary<string, string> headerDict = new Dictionary<string, string>();
headerDict.Add(function, value);
#if UNITY_EDITOR
headerDict.Add("UserID", userID.ToString());
#endif
UnityWebRequest www = UnityWebRequest.Post(downloadUrl, headerDict);
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log("<color=red>" + www.error + "</color>");
string errorMessage = www.error;
Debug.Log("There was an error getting the data list: " + errorMessage);
}
else
{
string result = HTMLEntityUtility.Decode(www.downloadHandler.text);
if (result.Length == 0)
{
Debug.Log("EMPTY");
}
else
{
//here i want that targetObject becomes the name of the given value
//the wanted line here is:
//User = JsonExtension.GetArrayFromJson<User>(result);
targetObject = JsonExtension.GetArrayFromJson<targetObject>(result);
queuedDownloads--;
}
}
yield return null;
}
I want to use this function to get the information with a webrequest and save that data in the user class.
to start this i want to use something like this:
//here I want to give which data it has to request, what value has to be used and in which variable this has to be stored
StartCoroutine(getDataArray("getUserData", "1", User));
I have multiple classes that i want to fill like this but I don't want 14 different functions that are the same except 1 variable.
Is there a way to make the targetObject from above function as the name of the variable I give in the coroutine?
Depends a bit on how that JsonExtension.GetArrayFromJson works of course but I think you could make your routine generic:
// The type parameter T is hereby extracted automatically from the reference
// you pass into "targetObject"
public IEnumerator getDataArray<T>(string function, string value, ref T targetObject)
{
queuedDownloads++;
var headerDict = new Dictionary<string, string>();
headerDict.Add(function, value);
#if UNITY_EDITOR
headerDict.Add("UserID", userID.ToString());
#endif
using(var www = UnityWebRequest.Post(downloadUrl, headerDict))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.LogError($"There was an error getting the data list: <color=red>{www.error}</color>");
return;
}
var result = HTMLEntityUtility.Decode(www.downloadHandler.text);
if (string.IsNullOrWhiteSpace(result))
{
Debug.LogError("The result is EMPTY!");
return;
}
// Here you use the generic type parameter "T"
targetObject = JsonExtension.GetArrayFromJson<T>(result);
}
queuedDownloads--;
}
You would call it like e.g.
User user;
SomeOtherClass someOtherClass;
StartCoroutine(getDataArray("getUserData", "1", user));
StartCoroutine(getDataArray("getSomeOtherData", "XY", someOtherClass));
as said passing in the type parameter T e.g. like
StartCoroutine(getDataArray<User>("getUserData", "1", user));
is not necessary / would be redundant as it is automatically extracted from the type of the object you pass into targetObject.
In general:
Note that right now you will be able to start your Coroutines with the correct references etc BUT ... you will never know if or when an actual result will be received.
You should probably rather implement a callback which handles the data once it is actually there e.g. like
public IEnumerator getDataArray<T>(string function, string value, Action<T> onSuccess)
{
[.....]
// Here you use the generic type parameter "T"
T targetObject = JsonExtension.GetArrayFromJson<T>(result);
queuedDownloads--;
// Invoke the callback
onSuccess?.Invoke(targetObject);
}
And call it like e.g.
StartCoroutine(getDataArray("getUserData", "1", HandleReceivedUser));
...
private void HandleReceivedUser(User user)
{
// do something with the user
}

Avoid changing the state of a variable when making use of 'Session' in C#

I want to save the result of a calculation (assigned to variable 'ans') for a second request. To do so, I use Session.
Even though it worked in the first request, when I want to call the variable in a second request (else-Statement) the following error occurs :"Cannot apply indexing with [] to an expression of type 'object'"
Does the state of the variable change through saving it to /accessing it from Session?
The format of 'ans' and 'ansTemp' for the second request is both times tuple of 5 items.
private Dictionary<string,int> DoCalculation(CalculatorModel model)
{
var ansTemp = Session["ANS"];
Dictionary<string, int> calculatedValues = new Dictionary<string, int>();
if (ansTemp == null)
{
if (model != null)
{
Class1 clsOne = new Class1(pathToLib, pathToPyFile);
try
{
var ans = clsOne.CallFunction("first_chart", rootPythonDir, model.age);
//Session.Add("ANS", ans);
Session["ANS"] = ans;
var assetAllocationCategory = ans[1];
}
catch (Exception ex) { }
}
}
else
{
var ans = ansTemp;
var assetAllocationCategory = ans[1];
}
}
Defining ansTemp as a dynamic instead of var when accessing the Session["ANS"] solved the problem.

c# reflection checking for attributes

I want to check whether a particular runtime type contains a property with a certain attribute like this:
public void Audit(MongoStorableObject oldVersion, MongoStorableObject newVersion)
{
if(oldVersion.GetType() != newVersion.GetType())
{
throw new ArgumentException("Can't Audit versions of different Types");
}
foreach(var i in oldVersion.GetType().GetProperties())
{
//The statement in here is not valid, how can I achieve look up of a particular attribute
if (i.GetCustomAttributes().Contains<Attribute>(MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
//else do some actual auditing work
}
}
But the statement is not valid, Can you tell me how to achieve lookup of a particular attribute on a property like this? Thanks,
Update:
I've found this which doesn't make intellisense complain:
if (i.GetCustomAttributes((new MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute()).GetType(),false).Length > 0) continue;
But I'm still not certain this will do what I want it too.
Change:
if (i.GetCustomAttributes().Contains<Attribute>(MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
to
if (i.GetCustomAttributes().Any(x=> x is MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
Revised:
public void Audit(MongoStorableObject oldVersion, MongoStorableObject newVersion)
{
if(oldVersion.GetType() != newVersion.GetType())
{
throw new ArgumentException("Can't Audit versions of different Types");
}
foreach(var i in oldVersion.GetType().GetProperties())
{
//The statement in here is not valid, how can I achieve look up of a particular attribute
if (i.GetCustomAttributes().Any(x=> x is MongoDB.Bson.Serialization.Attributes.BsonIgnoreAttribute)) continue;
//else do some actual auditing work
}
}
To clarify:
GetCustomAttributes() returns a list of attribute objects on the property. You need to iterate through them and check whether any of their TYPES are of BsonIgnoreAttribute.
private static void PrintAuthorInfo(System.Type t)
{
System.Console.WriteLine("Author information for {0}", t);
// Using reflection.
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is Author)
{
Author a = (Author)attr;
System.Console.WriteLine(" {0}, version {1:f}", a.GetName(), a.version);
}
}
}
http://msdn.microsoft.com/en-us/library/z919e8tw.aspx

Categories