Imagine I have created an object name of oItem. And this has a lot of attributes such as ItemCode, ItemName.....(around 300). And I want to assign new values to some of these attributes which were selected by the user of my application. User will give these attribute names as strings.
Ex: string attribute1 = "ItemCode".
Now what I want to do is assign a value to this attribute like:
oItem.attribute1 = "01234";
Is there a way to do something like this? I know you can convert a c# function call to a string. Therefore I think this should be possible too. Any help would be highly appreciated. Thanks!
UPDATE: This is a part of my SAP add-On. So these attributes are from a database table. The hard part is user can add more columns(user defined fields) to this which increase the number of attributes as well as I only know the original 300 attributes.
If you have 300 properties you should really refactor this class. I think that you can use a Dictionarystring, string> in this case.
Dictionary<string, string> Items = new Dictionary<string, string>
{
{"attribute1", "01234"}, {"attribute2", "56789"}, {"attribute3", "76543"}, // ...
};
You can access the values very efficiently:
string attribute1 = Items["attribute1"];
or add/modify them in a similar way:
Items["attribute4"] = "23456";
To make it easier for you there's a "dynamic" keyword in C#.
A sample from msdn dynamic object article:
// The class derived from DynamicObject.
public class DynamicDictionary : DynamicObject
{
// The inner dictionary.
Dictionary<string, object> dictionary
= new Dictionary<string, object>();
// This property returns the number of elements
// in the inner dictionary.
public int Count
{
get
{
return dictionary.Count;
}
}
// If you try to get a value of a property
// not defined in the class, this method is called.
public override bool TryGetMember(
GetMemberBinder binder, out object result)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
string name = binder.Name.ToLower();
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
// If you try to set a value of a property that is
// not defined in the class, this method is called.
public override bool TrySetMember(
SetMemberBinder binder, object value)
{
// Converting the property name to lowercase
// so that property names become case-insensitive.
dictionary[binder.Name.ToLower()] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
}
class Program
{
static void Main(string[] args)
{
// Creating a dynamic dictionary.
dynamic person = new DynamicDictionary();
// Adding new dynamic properties.
// The TrySetMember method is called.
person.FirstName = "Ellen";
person.LastName = "Adams";
// Getting values of the dynamic properties.
// The TryGetMember method is called.
// Note that property names are case-insensitive.
Console.WriteLine(person.firstname + " " + person.lastname);
// Getting the value of the Count property.
// The TryGetMember is not called,
// because the property is defined in the class.
Console.WriteLine(
"Number of dynamic properties:" + person.Count);
// The following statement throws an exception at run time.
// There is no "address" property,
// so the TryGetMember method returns false and this causes a
// RuntimeBinderException.
// Console.WriteLine(person.address);
}
}
// This example has the following output:
// Ellen Adams
// Number of dynamic properties: 2
It is really easy to do this for SAP B1 addon using c# with the provided library functions. Simply include using SAPbobsCOM;
And then you can do it as follows
yourobject.UserFields.Fields.Item("attribute").Value = "value";
Related
I am using reflection to instantiate a class and insert values in its nested properties.
Here's an example of what im trying to do:
https://dotnetfiddle.net/pBgeeV
private static void ApplyValueToProperty(object entityInstance, Type instanceType, string propName, string propValue)
{
// Current only supports an object inside another object.
// Future TODO> GO RECURSIVE TO FIND AND INSTANTIATE ALL NESTED PROPERTIES OR CLASSES!
if (propName.Contains("."))
{
string[] splittedName = propName.Split('.');
// Get type of current property
PropertyInfo currentProp = instanceType.GetProperty(splittedName[0]);
Type nestedPropertyType = currentProp.PropertyType;
// Get current object applied to this current prop
var otherObject = currentProp.GetValue(entityInstance);
if (otherObject == null)
{
// Create instance of nested property class and set its value to a new instance
otherObject = Activator.CreateInstance(nestedPropertyType);
currentProp.SetValue(entityInstance, otherObject);
}
SetSafePropertyValue(otherObject, nestedPropertyType, splittedName[1], propValue);
}
else
{
SetSafePropertyValue(entityInstance, instanceType, propName, propValue);
}
}
First I create the main class outside of this method scope. (Test class).
Then all other propName will have the value of nested properties or classes, example:
I will have a name like "OtherTest.AnotherTest.Name",
If 'Test' has another property of type 'OtherTest', it would create 'OtherTest' and then create 'AnotherTest' and then put value on correct 'Name'.
I would like to change the first if statement to be recursive and instantiate de correct class and insert values to the correct property. In the end I want to have a complete object created with my necessary properties instantiated and values applied.
Note: I want to avoid using GetProperties() because my class has lots of properties from base members, and it consumes a lot of memory.
Instead of this line
SetSafePropertyValue(otherObject, nestedPropertyType, splittedName[1], propValue);
you could do this
ApplyValueToProperty(otherObject, otherObject.GetType(), string.Join(".", splittedName.Skip(1)), propValue);
The idea is to recursivly call ApplyValueToProperty with the first part of the propName dropped hence the Skip(1) in string.Join(".", splittedName.Skip(1)), this will drop the first part again and again till we hit the following condition with false
if (propName.Contains("."))
The else part of this if will stop the recursion.
Story
I'm trying to write a generic method which combines property names, types and content value to generate a unique string for the value held by the object passed.
The idea is to generate a unique SHA3-512 Hash based on the generated string sequence which can be used to compare objects on generic bases by looking at their content.
Example
Let's say we have a class like this ...
class MyClass {
private Int32 Id = 5;
public String Name = "some string";
protected DateTime CreateDate = DateTime.Parse("2017-08-21 15:00:07");
}
... and the mentioned method to generate the unique string
static String GetContentString<T>(T obj) where T : class {
...
}
In theory this should work somewhat like this:
var myObj = new MyClass();
var uniqueContentString = GetContentString(myObj);
Console.WriteLine(uniqueContentString);
>>> Id:Int32:5$Name:String:some string$CreateDate:DateTime:2017-08-21 15:00:07
Problem
I'm having difficulties building the GetContentString Method. This is what I have already:
Object obj = ... // given object
Type type = obj.GetType();
IList<PropertyInfo> propertyInfos = type.GetProperties().Where(x => x.CanRead).ToList(); // Marker #2
StringBuilder sb = new StringBuilder();
foreach (PropertyInfo pi in propertyInfos)
{
sb.Append(pi.Name);
sb.Append(":");
sb.Append(pi.PropertyType.Name);
sb.Append(":");
sb.Append(pi.GetValue(obj) ?? "[ISNULL]"); // Marker #1
sb.Append(":");
}
return sb.ToString();
I tried running the method for a few different types of values like "some string" or 1234 (Int32) and ran into a few issues.
Given a string, the method call throws an exception of type System.Reflection.TargetParameterCountException and the message Parameter count mismatch at #1. I found out that an optional index can be passed to an overloaded version of pi.GetValue(..) which then returns one of the single letters. But how do you know when to stop? If you call an index which doesn't exist it throwns an exception of the type System.Reflection.TargetInvocationException. How do you get the value of a string object using reflection?
Given an integer value, the method call doesn't find any properties at #2. Which brings up the question of how to get the value of an integer object using reflection?
And also some general questions; do you guys think this is a good approach to get a unique string? Is reflection the way to go here? Is it even possible to write a generic solution to this problem?
Without looking at reflection, how about JSON serialization with something that .net framework is able to ?
Reflection isn't something extremely fast and you'll run into issues at the first unhandled exception.
Then, you should do that recursivly if your objects can contains complex properties, wich is not a problem with json serialization !
I have the following "calculation" class.
public class Foo
{
private int? _sum = 0;
public int Sum
{
get
{
if (_sum == null)
_sum = 1 + 1; //simple code to show "some" calculation happens...
return _sum.GetValueOrDefault();
}
}
}
In this example there is only 1 Field/Member but in my real class there are around 50 Members, that all look similar just with different value calculations.
In the class I also have a Recalc method.
This Recalc method does 4 things
Save the old values
set all fields to null
calls the getter of every member
Checks if the old values differ from the newvalues and does related stuff
I am not sure whats the best way to store the old values and check for changes with the new values.
My current implementation is this:
public string GetValuesKey()
{
//again this method only handles the 1 Member and not all 50 in real app its string.Format("{0}|{1}|{2}|...{49}|{50}|", ....);
return string.Format("{0}|", this.Sum);
}
public void Recalc()
{
var oldValues = GetValuesKey();
//set all fields to null
//call the getters
var newValues = GetValuesKey();
if (oldValues != newValues)
{
//something changed...
}
}
But with this code there is a memory/performance issue since I am doing boxing with the struct (decimal) writing to a reference type (string).
I kind of want to prevent doing 50 additional fields (something like _oldSum) for all the members.
I just need to check if any member has changed during the Recalc procedure.
Just in Case, I cannot do the following code.
public void Recalc()
{
var changes = false;
var oldValue = this.Sum;
_sum = null;
var newValue = this.Sum;
if (oldValue != newValue)
changes = true;
//check next member
oldValue = this.Sum2;
_sum2 = null;
newValue = this.Sum2;
if (oldValue != newValue)
changes = true;
//check next member and so on...
}
Since I need to set all fields to null first and only AFTER all of them have been set to null I can execute the getters, since the members are dependant on each other for exmple if the Sum Member would aggregate two other members and they havent been set to null first they would still have old values.
So I need a way to store something that represents all values before setting the fields null and after calling the getter of the members a way to check for changes.
Any help is welcome.
Edit:
Here is the code, I wrote to test performance/memory:
http://pastebin.com/3WiNJHyS
Instead of combining all values in a string and have some pefomance hit on that string construction - put all values in array (of decimal), then set all fields to null, make your calculation and compare arrays of old and new values.
If you don't want to write yourself all the 50 _oldValue fields, the only alternative is to use Reflection, that implies some boxing/unboxing, so performance will not be the best possible.
Anyway, in the following implementation I assume that in the Foo class the members that are involved in the calculation are all and the only ones that are properties of type decimal?.
Otherwise, we need a more complicated solution, with BindingFlags, and/or Attribute on every field/property involved, and so on.
public void Recalc()
{
var propertyInfos = GetType()
.GetProperties()
.Where(pInfo => pInfo.PropertyType.IsValueType);
var fieldInfos = GetType()
.GetFields()
.Where(fInfo => fInfo.FieldType.IsValueType);
//create a dictionary with all the old values
//obtained from the backing fields.
var oldValueDictionary = fieldInfos.ToDictionary(
fInfo => fInfo.Name,
fInfo => (decimal?)fInfo.GetValue(this));
//set all properties to null
foreach (var pInfo in propertyInfos)
pInfo.SetValue(this, null);
//call all the getters to calculate the new values
foreach (var pInfo in propertyInfos)
pInfo.GetValue(this);
//compare new field values with the old ones stored in the dictionary;
//if only one different is found, the if is entered.
if (fieldInfos.Any(fInfo =>
(decimal?)fInfo.GetValue(this) != oldValueDictionary[fInfo.Name]))
{
//do stuffs
}
}
As a final note, your class configuration is very strange. Are you sure that setting all the calculations in the getters is the best choice? Maybe you should re-think about you design. One task is to retrieve a property value (a getter), another task is to calculate something (starting from some value stored in the backing fields)...
I search on Google but the result is misunderstand.
This is a code flow:
public class Store
{
public dynamic GetList(List<string> propertyFields)
{
// Body here
// for in list propertyFields. Such as: ["Code", "Name", "Address", ...]
// dynamic result = ...
// result.Code = "value", result.Name = "abc", result.Address = "homeless", result.... = "..."
// return result
}
}
Method returns a dynamic object.
propertyFields is a list of fields name. When I pass 4 strings in list, dynamic has 4 property fields with value (later).
When I call this method:
Store store = new Store();
var rs = store.GetList(["Code","Name","Address"])
Console.WriteLine(rs[0].Code)
That is my point.
My question: Is it possible to do that in C#?
You have confused dynamic, which is a compiler feature that means "defer type analysis of uses of this object until runtime" with ExpandoObject which means "an object that can have properties added at runtime".
It is an understandable confusion; many languages that lack static type checking also have expando objects.
You need to take a look to ExpandoObject class.
Here is more detailed answer to your question:
Dynamic class creation
What are the true benefits of ExpandoObject?
Is there any way in which you can test whether an instance of a generic parameter in a generic method has been assigned to when it can be either a value or reference type? I would like to be able to do this in a generic method I have to persist types, where T is the instance and K is the type of the identifier field for that type (which all objects I am persisting have because they inherit from a base type). I do not want to limit K to being a value type. The code is something like this:
public static bool Save<T, K>(T instance)
{
// variable to hold object identifer
K instanceId = default(K);
PropertyInfo[] properties = typeof(T).GetProperties();
// loop through properties of the T
// if property is decorated with a specific attribute then assign to instanceId
// end loop
// check that we have a value assigned to instanceId other than default(K)
// if not return false otherwise continue to persist item
}
As K can be a value type checking if it is equal to default(K) results in an error as it relies on it being comparable. Is there a way around this?
Note that I have got around the need for this in the current case by placing a condition on the generic type T that it must inherit from the base type BaseObject so my question is about the general issue around generics and testing assignment.
If you're going to read from instanceId later, it has to be definitely assigned from a compiler point of view. I would assign it with default(K) and separately have a flag saying whether or not it's been given a useful value:
public static bool Save<T, K>(T instance)
{
bool idAssigned = false;
// variable to hold object identifer
K instanceId = default(K)
PropertyInfo[] properties = typeof(T).GetProperties();
foreach(PropertyInfo property in properties)
{
if (SomeCondition(property))
{
instanceId = GetId(property);
idAssigned = true;
}
}
if (!idAssigned)
{
return false;
}
Persist(whatever);
return true;
}
EDIT: Comparing the value of instanceId with any specific value is a non-starter, unless you know for a fact that the value will never be used for a "normal" value explicitly assigned to instanceId.
Basically you've got two bits of information here, so keep them separately - the flag is the way to go.