How to use FieldInfo.SetValue and GetValue properly? - c#

I am making an automatic saving and loading system that uses attributes. Inside my attribute, I have a method called SetSpecificField, which is supposed to do exactly that. It looks like this:
public static bool SetSpecificField(string className, string variableName, AutoLType autoLType)
{
bool worked = false;
try
{
worked = true;
foreach (Type type in Assembly.GetExecutingAssembly().GetTypes())
{
if(type.GetCustomAttribute<AutoLoadEnabled>(true) != null)
{
if(type.Name == className)
{
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.Name == variableName)
{
prop.SetValue(autoLType.Value.GetType(), autoLType.Value);
}
}
foreach(FieldInfo field in type.GetFields())
{
if(field.Name == variableName)
{
field.SetValue(autoLType.Value.GetType(), autoLType.Value)
}
}
}
}
}
}
catch (Exception ex)
{
worked = false;
}
return worked;
}
However it is giving me this error: ArgumentException: Field aloadBeg defined on type SaveManager is not a field on the target object which is of type System.RuntimeType. I dont know if I am doing this right. If I am doing this wrong can someone show me how to do it properly?
EDIT:
Here is the AutoLType class:
public class AutoLType
{
public static implicit operator AutoSType(AutoLType l)
{
if (l.PropertyOrField == PropOrField.Field)
{
return new AutoSType(l.Name, l.Value, l.Attribute, AutoSType.PropOrField.Field);
}
else
{
return new AutoSType(l.Name, l.Value, l.Attribute, AutoSType.PropOrField.Prop);
}
}
public enum PropOrField
{
Prop,
Field
}
public string Name { get; set; }
public object Value { get; set; }
public AutoLoad Attribute { get; set; }
public PropOrField PropertyOrField { get; set; }
public AutoLType(string name, object value, AutoLoad attr, PropOrField propOrField)
{
Name = name;
Value = value;
Attribute = attr;
PropertyOrField = propOrField;
}
}

Related

MetadataType and Attribute.IsDefined

I am using a database first approach and have a class SalesWallboard mapped to the database table.
SalesWallboard.cs:
namespace Wallboards.DAL.SchnellDb
{
public partial class SalesWallboard
{
public int Id { get; set; }
public string StaffName { get; set; }
...
}
}
I have created a MetadataType class for this to apply custom attributes
SalesWallboardModel.cs
namespace Wallboards.DAL.SchnellDb
{
[MetadataType (typeof(SalesWallboardModel))]
public partial class SalesWallboard
{
}
public class SalesWallboardModel
{
[UpdateFromEclipse]
public string StaffName { get; set; }
...
}
}
But in my code, when I check it using Attribute.IsDefined, it always throws false.
Code
var salesWallboardColumns = typeof(SalesWallboard).GetProperties();
foreach (var column in salesWallboardColumns)
{
if (Attribute.IsDefined(column, typeof(UpdateFromEclipse))) //returns false
{
...
}
}
I have checked the answer given here. But I don't understand the example.
Can someone please simplify this for me?
I would like to thank #elgonzo and #thehennyy for their comments and correcting my understanding with reflection.
I found what I was looking for here. But I had to make a few modifications shown below.
I created a method PropertyHasAttribute
private static bool PropertyHasAttribute<T>(string propertyName, Type attributeType)
{
MetadataTypeAttribute att = (MetadataTypeAttribute)Attribute.GetCustomAttribute(typeof(T), typeof(MetadataTypeAttribute));
if (att != null)
{
foreach (var prop in GetType(att.MetadataClassType.UnderlyingSystemType.FullName).GetProperties())
{
if (propertyName.ToLower() == prop.Name.ToLower() && Attribute.IsDefined(prop, attributeType))
return true;
}
}
return false;
}
I also got help from here because my type was in a different assembly.
Method GetType
public static Type GetType(string typeName)
{
var type = Type.GetType(typeName);
if (type != null) return type;
foreach (var a in AppDomain.CurrentDomain.GetAssemblies())
{
type = a.GetType(typeName);
if (type != null)
return type;
}
return null;
}
Then used it in my code like so
Code
var salesWallboardColumns = typeof(SalesWallboard).GetProperties();
foreach (var column in salesWallboardColumns)
{
var columnName = column.Name;
if (PropertyHasAttribute<SalesWallboard>(columnName, typeof(UpdateFromEclipse)))
{
...
}
}

Expression Bodied fields (read-only) in structs does not get copied using Reflection

I am trying to implement object deep/shallow cloning service using Reflection.
Using the function Clone<T> Simple class is being copied with all the required fields, but in case of SimpleStruct Computed field does not get copied.
What is the difference between struct and class when defining read-only fields, and how this can be solved ?
Thanks in advance.
public T Clone<T>(T source)
{
var obj = Activator.CreateInstance<T>();
var type = source.GetType();
foreach (var property in type.GetProperties())
{
if (!property.IsValid())
continue;
if (property.SetMethod != null)
{
property.SetValue(obj, property.GetValue(source));
}
}
foreach (var field in type.GetFields())
{
if (field.IsPublic == false || !field.IsValid())
continue;
field.SetValue(obj, field.GetValue(source));
}
return obj;
}
public struct SimpleStruct
{
public int I;
public string S { get; set; }
[Cloneable(CloningMode.Ignore)]
public string Ignored { get; set; }
public string Computed => S + I;
public SimpleStruct(int i, string s)
{
I = i;
S = s;
Ignored = null;
}
}
public class Simple
{
public int I;
public string S { get; set; }
[Cloneable(CloningMode.Ignore)]
public string Ignored { get; set; }
[Cloneable(CloningMode.Shallow)]
public object Shallow { get; set; }
public string Computed => S + I + Shallow;
}

C# Accessing custom attribute of owner object

How can I access the custom attribute of the parent or owner object.
Look at the FieldInfo property of the SQLFieldInfo struct
Here's a more detailed program that will compile and run that shows what I need.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Employee myclass = new Employee();
// Load from sql server...
myclass.Name = "Alain";
myclass.Age = 51;
//----
MessageBox.Show(myclass.Name.ToString()); // Should return Alain
MessageBox.Show(myclass.Age.FieldInfo.Type.ToString()); // Should output "int"
}
}
// This next class is generated by a helper exe that reads SQL table design and create the class from it
[SQLTableAttribute(DatabaseName = "Employees", Schema = "dbo", TableName = "Employees")]
public class Employee
{
[SQLFieldAttribute(FieldName = "ID", Type = SqlDbType.Int)]
public SQLFieldInfo<int> ID { get; set; }
[SQLFieldAttribute(FieldName = "Name", Type = SqlDbType.NVarChar, Size = 200)]
public SQLFieldInfo<String> Name { get; set; }
[SQLFieldAttribute(FieldName = "Age", Type = SqlDbType.Int)]
public SQLFieldInfo<int> Age { get; set; }
}
public struct SQLFieldInfo<T>
{
private readonly T value;
public SQLFieldInfo(T Value)
{
this.value = Value;
}
public static implicit operator SQLFieldInfo<T>(T Value)
{
return new SQLFieldInfo<T>(Value);
}
public T Value
{
get
{
return this.value;
}
}
public override string ToString()
{
return this.value.ToString();
}
public SQLFieldAttribute FieldInfo
{
get
{
// Need to retreive the attribute class of the parent or declaring member
return null;
}
}
}
// Holds the sql field information
public class SQLFieldAttribute : Attribute
{
public string FieldName { get; set; }
public SqlDbType Type { get; set; }
public bool AllowNull { get; set; }
public int Size { get; set; }
}
// Holds the sql table information
public class SQLTableAttribute : Attribute
{
public string DatabaseName { get; set; }
public string Schema { get; set; } = "dbo";
public string TableName { get; set; }
}
Thank you!
Alain
My data class is as follows (should be fairly translatable to A above):
public class Foo
{
[Argument(Help = "Name", AssignmentDelimiter = "=")]
public string Name
{
get;
set;
}
}
A helper class is responsible of reading attribute values of objects:
static public string GetCommandLineDelimiter<T>(Expression<Func<T>> property)
{
if(property != null)
{
var memberExpression = (MemberExpression)property.Body;
string propertyName = memberExpression.Member.Name;
PropertyInfo prop = typeof(Arguments).GetProperty(propertyName);
if(prop != null)
{
object[] dbFieldAtts = prop.GetCustomAttributes(typeof(ArgumentAttribute), true);
if(dbFieldAtts.Length > 0)
{
return ((ArgumentAttribute)dbFieldAtts[0]).AssignmentDelimiter;
}
}
}
return null;
}
To use it, simply:
string delimiter = GetCommandLineDelimiter(() => myObject.Name);
That will get the attribute value of AssignmentDelimiter on property Name, i.e. "=".
First, MSDN is your friend.
Then, if you want to get the attributes for ancestors just specify true in the inherit flag of the method:
var attribute = typeof(A).GetProperty("myprop").GetCustomAttributes(true)
.OfType<MycustomAttrib>().FirstOrDefault();
This works. I am doing a lazy initialization of a reference to the custom attribute by using reflection to look at all the properties of all the types.
public class MycustomAttribAttribute : Attribute
{
public MycustomAttribAttribute(string name)
{
this.Name=name;
}
public string Name { get; private set; }
}
class A
{
public A() { MyProp=new B(); }
[MycustomAttrib(name: "OK")]
public B MyProp { get; set; }
}
class B
{
private static Lazy<MycustomAttribAttribute> att = new Lazy<MycustomAttribAttribute>(() =>
{
var types = System.Reflection.Assembly.GetExecutingAssembly().DefinedTypes;
foreach(var item in types)
{
foreach(var prop in item.DeclaredProperties)
{
var attr = prop.GetCustomAttributes(typeof(MycustomAttribAttribute), false);
if(attr.Length>0)
{
return attr[0] as MycustomAttribAttribute;
}
}
}
return null;
});
public string MyProp2
{
get
{
return att.Value.Name;
}
}
}
class Program
{
static void Main(string[] args)
{
// Finds the attribute reference and returns "OK"
string name = (new A()).MyProp.MyProp2;
// Uses the stored attribute reference to return "OK"
string name2 = (new A()).MyProp.MyProp2;
}
}

Is it possible to set the property of the condition dynamically?

Normally when I want for example to find the first or default item of a List I use this way:
myList.SingleOrDefault(x=>x.MyPropery01 == "myCondition");
However, I would like to know if it is possible, for example by reflection, if I set the the property MyProperty dynamically, something like:
myList.SingleOrDefault(x=>x.GetType().GetProperty("MyProperty01") == "myCondition");
Because sometimes I need to search for MyProperty01, sometimes for MyProperty02, MyProperty03, etc..
EDIT: in visual studio I get this error:
"Operator '==' can't be applied to operands of type System.reflection.PropertyInfo and string".
Yeah you can do that. You were pretty close, here is a demo you can drop in linqpad. Note that the important part is
Single(l => l.GetType().GetProperty(prop).GetValue(l).ToString() == "Condition")
void Main()
{
var myList = Enumerable.Range(0,10).Select(i => new Xmas(i,"name"+i)).ToList();
string prop = "name";
Console.WriteLine(myList.Single(l => l.GetType().GetProperty(prop).GetValue(l).ToString() == "name6").name);
}
public class Xmas
{
public int id { get; set; }
public string name { get; set; }
public Xmas( int id, string name )
{
this.id = id;
this.name = name;
}
}
Working example:
public class Apple
{
public string Color { get; set; }
}
public List<Apple> ApplesList {get;set;}
public void Process()
{
PropertyInfo pi = typeof(Apple).GetProperty("Color");
ApplesList = ApplesList.Where(r => (string)pi.GetValue(r) == "Red").ToList();
}
You could also write an Extension method, that allow to get the property on every object, returning null when it doesn't exist, or doesn't have a GetMethod. You could keep a Cache if you want
public static class ObjectExtension
{
static IDictionary<KeyValuePair<Type, string>, PropertyInfo> propertyCache = new Dictionary<KeyValuePair<Type, string>, PropertyInfo>();
public static object GetProperty(this object source, string propertyName, bool useCache = true)
{
if (source == null)
{
return null;
}
var sourceType = source.GetType();
KeyValuePair<Type, string> kvp = new KeyValuePair<Type, string>(sourceType, propertyName);
PropertyInfo property = null;
if (!useCache || !propertyCache.ContainsKey(kvp))
{
property = sourceType.GetProperty(propertyName);
if (property == null)
{
return null;
}
var get = property.GetGetMethod();
if (get == null)
{
return null;
}
if (useCache)
{
propertyCache.Add(kvp, property);
}
}
else
{
property = propertyCache[kvp];
}
return property.GetValue(source, null);
}
public static T GetProperty<T>(this object source, string propertyName)
{
object value = GetProperty((object)source, propertyName);
if (value == null)
{
return default(T);
}
return (T)value;
}
}
A small test class could then be:
public class Item
{
public string MyProperty { get; set; }
public string MyProperty3 { get; set; }
public string MyProperty2 { protected get; set; }
public Item()
{
MyProperty = "Test propery";
MyProperty3 = "Test property 3";
MyProperty2 = "Yoohoo";
}
}
With a main class for testing
class Program
{
static void Main(string[] args)
{
Item item = new Item();
for (int x = 0; x < 4; x++)
{
string key = "MyProperty" + (x > 0 ? x.ToString() : "");
string value = item.GetProperty<string>(key);
Console.WriteLine("Getting {0} = {1}", key, value);
}
Console.ReadLine();
}
}
which gives the expectable output of:
Getting MyProperty = Test propery
Getting MyProperty1 =
Getting MyProperty2 =
Getting MyProperty3 = Test property 3

How to discover all static fields of a certain type within a UITypeEditor?

I want people who use my custom control to choose a constant with a simple dropdown. The control should be reusable and therefor resides in a library so it can't access an enum, and the constants could in fact be defined anywhere.
Problem is that AppDomain.CurrentDomain.GetAssemblies() does not contain the current project you are designing in. And when I create a separate project it sometimes works but mostly it doesn't. And with doesn't work I mean: It can't find any classes/constants defined in the library, so they don't show up in the dropdown.
Everything else already works. The dropdown works. Code generation is a but clunky but it works. And I can always find constants defined in TestControl itself.
It seems like the designer itself doesn't always load all the assemblies/projects which are available. So how can I load the project which I need within the context of the designer/visual studio itself? Or how do I reference/find other projects within my list of dependencies from within the UITypeEditor?
I have created my own TestControl:
public partial class TestControl : UserControl, IHasTags
{
public static readonly ITestItem A = new TestItemImpl(null, "A", "Aap");
public static readonly ITestItem B = new TestItemImpl(null, "B", "B");
public static readonly ITestItem C_xx = new TestItemImpl(null, "C", "C");
public static readonly ITestItem D = new TestItemImpl(null, "D", "D");
[Editor(typeof(ItemEditor), typeof(UITypeEditor))]
public ITestItem item { get; set; }
public TestControl()
{
InitializeComponent();
}
}
With my own editor:
public class ItemEditor : UITypeEditor
{
private IWindowsFormsEditorService _editorService;
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
// drop down mode (we'll host a listbox in the drop down)
return UITypeEditorEditStyle.DropDown;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
_editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
// use a list box
ListBox lb = new ListBox();
lb.SelectionMode = SelectionMode.One;
lb.SelectedValueChanged += OnListBoxSelectedValueChanged;
lb.Width = 300;
// get the analytic object from context
// this is how we get the list of possible benchmarks
TestControl ctrl = (TestControl)context.Instance;
// Add all test items
IList<StaticFieldInfo<ITestItem>> items = ClassHelper.getStaticFields<IHasTags, ITestItem>();
foreach (StaticFieldInfo<ITestItem> item in items)
{
lb.Items.Add(item);
}
// show this model stuff
_editorService.DropDownControl(lb);
if (lb.SelectedItem == null) // no selection, return the passed-in value as is
return value;
StaticFieldInfo<ITestItem> result = (StaticFieldInfo<ITestItem>)lb.SelectedItem;
return result.value;
}
private void OnListBoxSelectedValueChanged(object sender, EventArgs e)
{
// close the drop down as soon as something is clicked
_editorService.CloseDropDown();
}
}
And I collect all constants with the following helper:
public class StaticFieldInfo<T>
{
public StaticFieldInfo(FieldInfo fieldInfo, T value)
{
this.fieldInfo = fieldInfo;
this.value = value;
}
public T value { get; private set; }
public FieldInfo fieldInfo { get; private set; }
public override string ToString()
{
return value.ToString() + " (" + fieldInfo.DeclaringType.FullName + "." + fieldInfo.Name + ")";
}
}
public class ClassHelper
{
public static IList<StaticFieldInfo<T>> getStaticFields<C, T>()
{
Type classType = typeof(C);
Type fieldType = typeof(T);
List<StaticFieldInfo<T>> result = new List<StaticFieldInfo<T>>();
foreach (Assembly asm in AppDomain.CurrentDomain.GetAssemblies())
{
try
{
foreach (Type t in asm.GetTypes())
{
if (classType.IsAssignableFrom(t))
{
foreach (FieldInfo f in t.GetFields())
{
if (f.IsStatic && fieldType.IsAssignableFrom(f.FieldType))
{
T value = (T)f.GetValue(null);
if (value != null)
{
result.Add(new StaticFieldInfo<T>(f, value));
}
}
}
}
}
}
catch (ReflectionTypeLoadException)
{
// Ignore errors
}
}
return result;
}
}
The rest of the code (less important):
public interface IHasTags
{
}
[TypeConverter(typeof(ITestItemConverter))]
public interface ITestItem
{
string driver { get; set; }
string tag { get; set; }
string name { get; set; }
}
public class ITestItemConverter : TypeConverter
{
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
// we only know how to convert from to a string
return typeof(string) == destinationType;
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value != null)
{
return value.ToString();
}
return "(none)";
}
}
[DesignerSerializer(typeof(ItemSerializer), typeof(CodeDomSerializer))]
public class TestItemImpl : ITestItem
{
public TestItemImpl()
{
}
public TestItemImpl(string driver, string tag, string name)
{
this.driver = driver;
this.tag = tag;
this.name = name;
}
public override string ToString()
{
return name;
}
public string driver { get; set; }
public string tag { get; set; }
public string name { get; set; }
}
public class ItemSerializer : CodeDomSerializer
{
public override object Deserialize(IDesignerSerializationManager manager, object codeObject)
{
// This is how we associate the component with the serializer.
CodeDomSerializer baseClassSerializer = (CodeDomSerializer)manager.
GetSerializer(typeof(TestItemImpl).BaseType, typeof(CodeDomSerializer));
/* This is the simplest case, in which the class just calls the base class
to do the work. */
return baseClassSerializer.Deserialize(manager, codeObject);
}
public override object Serialize(IDesignerSerializationManager manager, object value)
{
/* Associate the component with the serializer in the same manner as with
Deserialize */
CodeDomSerializer baseClassSerializer = (CodeDomSerializer)manager.
GetSerializer(typeof(TestItemImpl).BaseType, typeof(CodeDomSerializer));
object codeObject = baseClassSerializer.Serialize(manager, value);
/* Anything could be in the codeObject. This sample operates on a
CodeStatementCollection. */
if (codeObject is CodeStatementCollection)
{
CodeStatementCollection statements = (CodeStatementCollection)codeObject;
StaticFieldInfo<ITestItem> field = null;
foreach (StaticFieldInfo<ITestItem> item in ClassHelper.getStaticFields<IHasTags, ITestItem>())
{
if (value == item.value)
{
field = item;
break;
}
}
// If field can be found (should be always)
if (field != null)
{
statements = new CodeStatementCollection();
statements.Add(new CodeSnippetExpression(typeof(ITestItem).FullName + " " + this.GetUniqueName(manager, value) + " = " + typeof(TestControl).FullName + "." + field.fieldInfo.Name));
codeObject = statements;
}
}
return codeObject;
}
}

Categories