I have some existing models written and I was trying to map them with NHibernate (version 5). As it happened, they are properties exposed as IEnumerable<T> with backing fields declared as ICollection<T>, like so:
public class Encounter
{
public virtual String Title { get; protected set; } = null!;
public virtual IEnumerable<Conversation> Conversations => _conversations.AsEnumerable();
private ICollection<Conversation> _conversations = new HashSet<Conversation>();
protected Encounter() { }
// ...
}
I'm trying to use ConventionModelMapper as much as possible, and so far so good, but I found it doesn't seem to know what to do with ICollection<T>, and I get TransientObjectExceptions when it tries to persist objects in those collections. If I change them to ISet<T>s, it maps by convention just fine... but it seems like this should be something I should be able to easily tell the ConventionModelMapper to do, since I have a number of these ICollection<T> members.
I'm not finding an easy way... This is part of the way there, but overrides the existing IsSet() logic, so I'd have to add back in conditions for ISet members at least, and it doesn't work for my IEnumerable<T> properties with backing fields (which NH is clearly able to deal with because it correctly picks up ISet backing fields)...
var mapper = new ConventionModelMapper();
mapper.IsSet((memberInfo, b1) =>
{
var memberType = memberInfo.GetPropertyOrFieldType();
if (memberType.IsGenericType)
{
return memberType.GetGenericInterfaceTypeDefinitions().Contains(typeof(ICollection<>));
}
return false;
});
Seems like this should be simpler, am I missing an event hook?
After some further experimentation and research, I came up with this, that works... but it seems overwrought and not very DRY. Before I refactor this, someone please tell me there's a better way?
// All this just to get NHibernate to map ICollection as "Set" by default?
mapper.IsSet((memberInfo, b1) =>
{
IEnumerable<Type> typeDefs;
Type memberType = memberInfo.GetPropertyOrFieldType();
if (memberType.IsGenericType)
{
typeDefs = memberType.GetGenericInterfaceTypeDefinitions();
if(typeDefs.Contains(typeof(ISet<>)) || typeDefs.Contains(typeof(ICollection<>)))
{
return true;
}
}
const BindingFlags defaultBinding = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly;
var fieldInfo = (from ps in PropertyToField.DefaultStrategies.Values
let fi = memberInfo.DeclaringType.GetField(ps.GetFieldName(memberInfo.Name), defaultBinding)
where fi != null
select fi).FirstOrDefault();
if (fieldInfo != null)
{
memberType = fieldInfo.GetPropertyOrFieldType();
if (memberType.IsGenericType)
{
typeDefs = memberType.GetGenericInterfaceTypeDefinitions();
if (typeDefs.Contains(typeof(ISet<>)) || typeDefs.Contains(typeof(ICollection<>)))
return true;
}
}
return false;
});
This is near-copypasta and uses code from NHibernate and its included PropertyToField utility class. I mostly understand it, but it's just... verbose... seems like there should be a better option. Again, am I missing a simple hook somewhere?
Related
This question already has answers here:
How enumerate all classes with custom class attribute?
(8 answers)
Closed 3 years ago.
I'm working on a command tool in C#, although not for a terminal command-line. I have read the documentation on reflection and attributes but I'm not sure exactly what the "right" way to go about this is.
The problem isn't very complicated, but it needs to be easily extended. I need to just have Commands that are picked up and loaded in where their triggering strings are checked and if they match, methods are called. How I went about it just as a proof-of-concept was:
[System.AttributeUsage(System.AttributeTargets.Class)]
public class CommandAttribute : Attribute
{
public string Name { get; private set; } //e.g Help
public string TriggerString { get; private set; } //e.g. help, but generally think ls, pwd, etc
public CommandAttribute(string name, string triggerStrings)
{
this.Name = name;
this.TriggerString = triggerString;
}
}
Now, I decorated the class and it will implement methods from an interface. Eventually there will be many commands and my idea is to make it easy for someone with minimal programming experience to jump in and make a command.
using Foo.Commands.Attributes;
using Foo.Infrastructure;
namespace Foo.Commands
{
[Command("Help", "help")]
public class Help : IBotCommand
{
// as an example, if the message's contents match up with this command's triggerstring
public async Task ExecuteAction()
}
}
This gets injected into the console app where it will load the commands and get passed messages
public interface ICommandHandler
{
Task LoadCommands();
Task CheckMessageForCommands();
}
Then, everything with a matching attribute will get loaded in and when a message is received, it will check its contents against all CommandAttribute decorated classes' triggering strings, and if it matches, call the method ExecuteAction on that command class.
What I've seen/tried: I understand how to use reflection to get custom attribute data, however I'm confused as to getting the methods and calling them, and how all of this should be configured to be fairly performant with reflection being used. I see CLI tools and chat bots that use a similar method, I just cannot peek into their handlers to see how these get loaded in and I can't find a resource that explains how to go about accessing the methods of these classes. Attributes may not be the right answer here but I'm not sure how else to go about it.
Really, my main question is:
How do I setup The CommandHandler to load all of the attribute-decorated classes and call their methods, and how they should be instantiated within it. I know the second piece may be a bit more subjective but would newing them up be improper? Should they somehow be added to DI?
My solution ended up just using the Activator and lists. I still need to tweak this for performance and run more extensive stress tests, but here is my quick code for it:
// for reference: DiscordCommandAttribute is in Foo.Commands library where all the commands are, so for now it's the target as I removed the base class
// IDiscordCommand has every method needed, so casting it as that means down the line I can call my methods off of it. The base class was just for some reflection logic I was testing and was removed, so it's gone
public void LoadCommands() // called in ctor
{
var commands =
from t in typeof(DiscordCommandAttribute).Assembly.GetTypes()
let attribute = t.GetCustomAttribute(typeof(DiscordCommandAttribute), true)
where attribute != null
select new { Type = t, Attribute = attribute };
foreach (var obj in commands)
{
_commandInstances.Add((IDiscordCommand)Activator.CreateInstance(obj.Type));
_commandAttributes.Add(obj.Attribute as DiscordCommandAttribute);
}
}
There is probably a more sugary way to handle adding the objects to the lists, and some other data structure besides Lists might be more suitable, I'm just not sure if HashSet is right because it's not a direct Equals call. Eventually I will genericize the interface for this class and hide all of this logic in a base class. Still a lot of work to do.
Currently, just putting a stopwatch start before calling LoadCommands shows that the entire load takes 4ms. This is with 3 classes and a pretty anemic attribute, but I'm not too worried about the scale as I want any overhead on launch and not during command handling.
Using some code I wrote for this answer, you can find all types that implement an interface, e.g. IBotCommand, and then retrieve the custom attribute:
public static class TypeExt {
public static bool IsBuiltin(this Type aType) => new[] { "/dotnet/shared/microsoft", "/windows/microsoft.net" }.Any(p => aType.Assembly.CodeBase.ToLowerInvariant().Contains(p));
static Dictionary<Type, HashSet<Type>> FoundTypes = null;
static List<Type> LoadableTypes = null;
public static void RefreshLoadableTypes() {
LoadableTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetLoadableTypes()).ToList();
FoundTypes = new Dictionary<Type, HashSet<Type>>();
}
public static IEnumerable<Type> ImplementingTypes(this Type interfaceType, bool includeAbstractClasses = false, bool includeStructs = false, bool includeSystemTypes = false, bool includeInterfaces = false) {
if (FoundTypes != null && FoundTypes.TryGetValue(interfaceType, out var ft))
return ft;
else {
if (LoadableTypes == null)
RefreshLoadableTypes();
var ans = LoadableTypes
.Where(aType => (includeAbstractClasses || !aType.IsAbstract) &&
(includeInterfaces ? aType != interfaceType : !aType.IsInterface) &&
(includeStructs || !aType.IsValueType) &&
(includeSystemTypes || !aType.IsBuiltin()) &&
interfaceType.IsAssignableFrom(aType) &&
aType.GetInterfaces().Contains(interfaceType))
.ToHashSet();
FoundTypes[interfaceType] = ans;
return ans;
}
}
}
public static class AssemblyExt {
//https://stackoverflow.com/a/29379834/2557128
public static IEnumerable<Type> GetLoadableTypes(this Assembly assembly) {
if (assembly == null)
throw new ArgumentNullException("assembly");
try {
return assembly.GetTypes();
}
catch (ReflectionTypeLoadException e) {
return e.Types.Where(t => t != null);
}
}
}
Note: If you create types at runtime, you will need to run RefreshLoadableTypes to ensure they get returned.
If you are concerned about IBotCommand implementors existing without the CommandAttribute, you can filter the ImplementingTypes, otherwise:
var botCommands = typeof(IBotCommand)
.ImplementingTypes()
.Select(t => new { Type = t, attrib = t.GetTypeInfo().GetCustomAttribute<CommandAttribute>(false) })
.Select(ta => new {
ta.Type,
TriggerString = ta.attrib.TriggerString
})
.ToDictionary(tct => tct.TriggerString, tct => tct.Type);
With an extension method for your command Types:
public static class CmdTypeExt {
public static Task ExecuteAction(this Type commandType) {
var cmdObj = (IBotCommand)Activator.CreateInstance(commandType);
return cmdObj.ExecuteAction();
}
}
You can use the Dictionary like:
var cmdString = Console.ReadLine();
if (botCommands.TryGetValue(cmdString, out var cmdType))
await cmdType.ExecuteAction();
Overall, I might suggest having a method attribute and having static methods in static classes for commands, so multiple (related?) commands can be bundled in a single class.
PS My command interpreters have help associates with each command, and categories to group commands, so perhaps some more attribute parameters and/or another IBotCommand method to return a help string.
I'm working with a client's API that has multiple different 'services' (around 10 so far), each one imports as its own namespace. Part of their standard API call pattern involves returning an array of error messages:
public class Error {
public String ErrorMessage {get;set}
public int errorNumber {get;set}
..etc
}
I've been trying to clean up and unify our handling of those messages. I tried to make a single function to handle them, eg:
void CheckErrors(List<Error> errors) {
if(errors != null && errors.Count() > 0)
throw new Exception(errors.First().ErrorMessage);
}
(the actual function is more complex but that gives the general idea)
However, it turns out that every one of their services has its own (identical) definition of this Error class. In C++ I could just template this function and it would work fine, or in a dynamic language it'd just work, but in C# I haven't been able to find a way to do this without making 10+ copies of the same function, each with a different namespace on the Error type.
In my own code I could just add an interface to these classes, but since it's not my code I don't think you can do that in C#? I could make a wrapper class that inherits from each of these and implements the interface, but I'm still stuck with duplicate code for every namespace/service.
Is there any cleaner way to handle this?
You could consider using a late binding solution either using reflection or dynamic. Both have the same drawback: you loose compile time type safety but if its a very isolated and contained piece of code it should be tolerable:
Reflection
void CheckErrors(List<object> errors) {
if(errors != null && errors.Count() > 0)
{
var firstError = errors.First();
throw new Exception(
firstError.GetType()
.GetProperty("ErrorMessage")
.GetValue(firstError)
.ToString()); }
Dynamic
void CheckErrors(List<dynamic> errors) {
if(errors != null && errors.Count() > 0)
throw new Exception(errors.First().ErrorMessage); }
Bear with me... you may need yet another Error class that is identical in properties to their Error class, but that you define in your namespace.
Your method CheckErrors uses your definition of Error.
Finally, you can use AutoMapper to map between their Error types and yours. This is pretty much exactly what AutoMapper is designed for. Since all your contracts are identical, the AutoMapper configuration should be trivial. Of course, you incur some runtime expense of mapping, but I think this would lead to the cleanest statically typed solution given that you can't change their interfaces.
The AutoMapper config + usage will look something like this:
//See AutoMapper docs for where to put config, it shouldn't happen on every call
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<TheirApi.Error, MyNamespace.MyErrorDefinition>();
}
var mapper = config.CreateMapper();
MyErrorDefinition myErrors = mapper.Map<List<MyErrorDefinition>>(listOfTheirErrorObjects);
CheckErrors(myErrors);
Another way is to use lambdas:
void CheckErrors<T>(IEnumerable<T> errors, Func<T,string> getMessage)
{
if (errors?.Count() > 0) throw new Exception(getMessage(errors.First()));
}
Then call it like this:
CheckErrors(errors, (e) => e.ErrorMessage);
I would define my own Error class that has a constructor that accepts any error object from your vendor and converts it. For example:
public class Error
{
public string Message { get; private set; }
public int ErrorNumber { get; private set; }
public Error(object vendorError)
{
var t = vendorError.GetType();
foreach (var source in t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
foreach (var dest in typeof(Error).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
if (dest.Name != source.Name) continue;
if (dest.PropertyType != source.PropertyType) continue;
dest.SetValue(this, source.GetValue(vendorError, null));
}
}
}
}
Then when you have an error list from your third party library, you can convert it using LINQ:
var myErrorList = vendorErrorList.Select( e => new Error(e) );
And now you can access the properties per normal.
See my example on DotNetFiddle
This page on the PostSharp website has the following teaser:
One of the common situations that you will encounter is the need to implement a specific interface on a large number of classes. This may be INotifyPropertyChanged, IDispose, IEquatable or some custom interface that you have created.
I'd like to write a custom aspect that implements a general version of IEquatable based on the properties of the class it's applied to (preferably at compile-time instead of by using reflection at runtime). It would be good to just be able to add an attribute to a simple class rather than having to implement a custom method each time. Is that possible? I'd hope so, since it's specifically called out in this introduction, but I haven't been able to track down any example code.
I've seen this example from the PostSharp website that includes an example of introducing the IIdentifiable interface. But it just returns a GUID that's independent of the class that the new interface is added to.
Is there a way to construct a custom attribute that implements IEquatable based on the properties of the type that it's applied to (i.e. making two instances equal if all of their properties are equal)?
I've found a solution using T4 templates but would like to know if the same can be achieved using PostSharp.
Edit:
To be clear, I'd like to be able to write something like this:
[AutoEquatable]
public class Thing
{
int Id { get; set; }
string Description { get; get; }
}
and have it automatically converted to this:
public class Thing
{
int Id { get; set; }
string Description { get; get; }
public override bool Equals(object other)
{
Thing o = other as Thing;
if (o == null) return false;
// generated in a loop based on the properties
if (!Id.Equals(o.Id)) return false;
if (!Description.Equals(o.Description)) return false;
return true;
}
}
This is possible with PostSharp 4.0 using the following code;
[PSerializable]
class EquatableAttribute : InstanceLevelAspect, IAdviceProvider
{
public List<ILocationBinding> Fields;
[ImportMember("Equals", IsRequired = true, Order = ImportMemberOrder.BeforeIntroductions)]
public Func<object, bool> EqualsBaseMethod;
[IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
public new bool Equals(object other)
{
// TODO: Define a smarter way to determine if base.Equals should be invoked.
if (this.EqualsBaseMethod.Method.DeclaringType != typeof(object) )
{
if (!this.EqualsBaseMethod(other))
return false;
}
object instance = this.Instance;
foreach (ILocationBinding binding in this.Fields)
{
// The following code is inefficient because it boxes all fields. There is currently no workaround.
object thisFieldValue = binding.GetValue(ref instance, Arguments.Empty);
object otherFieldValue = binding.GetValue(ref other, Arguments.Empty);
if (!object.Equals(thisFieldValue, otherFieldValue))
return false;
}
return true;
}
// TODO: Implement GetHashCode the same way.
public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
{
Type targetType = (Type) targetElement;
FieldInfo bindingField = this.GetType().GetField("Fields");
foreach (
FieldInfo field in
targetType.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic))
{
yield return new ImportLocationAdviceInstance(bindingField, new LocationInfo(field));
}
}
}
I'm afraid this can not be done with PostSharp. PostSharp "injects" aspects code in your clases but you have to code the aspects. The key here is indetify common behavior and cross cutting concern in your system and model it as Aspects.
In the example of IIdentifiable you can see how GUID is a unique identifier that can be use by a lot of different classes in your system. It is common code, it is cross cutting concern and you find yourself REPEATING code in all your class entities so Identificable can be modeled as Aspect and get rid of repeating code.
As diferent classes has diferent Equals implementation you can not "deatach" (convert to aspect) the implementation of Equals. Equals is not a common behavior. Equals is not cross cutting concern. Equals can not be an Aspect (without reflection).
I'm working on a business application that use the PropertyGrid. My project leader want me to localize the texts in the PropertyGrid at runtime. Hurray!!! irony
I have tried many days to localize the PropertyGrid. But I have trouble changing the attributes Description and Category at runtime. Changing the DisplayName works fine.
I have made a simple example to reproduce the issue: Create a Windows Form application and from the ToolBox add a PropertyGrid and a Button with default settings.
Here is the class I would like to display in the PropertyGrid:
class Person
{
int age;
public Person()
{
age = 10;
}
[Description("Person's age"), DisplayName("Age"), Category("Fact")]
public int Age
{
get { return age; }
}
}
In the Form's constructor; I create the Person object and display it in the PropertyGrid.
public Form1()
{
InitializeComponent();
propertyGrid1.SelectedObject = new Person();
}
The button is used to change the DisplayName, Description and Category attributes at runtime.
private void button1_Click(object sender, EventArgs e)
{
SetDisplayName();
SetDescription();
SetCategory();
propertyGrid1.SelectedObject = propertyGrid1.SelectedObject; // Reset the PropertyGrid
}
The SetDisplayName() method works fine and actually changes the DisplayName of the property in runtime!
private void SetDisplayName()
{
Person person = propertyGrid1.SelectedObject as Person;
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(person)["Age"];
DisplayNameAttribute attribute = descriptor.Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
FieldInfo field = attribute.GetType().GetField("_displayName", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(attribute, "The age");
}
SetDescription() and SetCategory() methods are almost identical to the SetDisplayName() method, except for some type changes and strings to access the private member of each attributes.
private void SetDescription()
{
Person person = propertyGrid1.SelectedObject as Person;
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(person)["Age"];
DescriptionAttribute attribute = descriptor.Attributes[typeof(DescriptionAttribute)] as DescriptionAttribute;
FieldInfo field = attribute.GetType().GetField("description", BindingFlags.NonPublic |BindingFlags.Instance);
field.SetValue(attribute, "Age of the person");
}
private void SetCategory()
{
Person person = propertyGrid1.SelectedObject as Person;
PropertyDescriptor descriptor = TypeDescriptor.GetProperties(person)["Age"];
CategoryAttribute attribute = descriptor.Attributes[typeof(CategoryAttribute)] as CategoryAttribute;
FieldInfo[] fields = attribute.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo field = attribute.GetType().GetField("categoryValue", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(attribute, "Info");
}
Both SetDescription() and SetCategory() methods compile and run but don't effext the ProperytGrid. After the last line of each method you can use the IntelliSense to see that the the Attribute object (DescriptionAttribute and CategoryAttribute) has a member that has changed.
After running these three methods and resetting the PropertyGrid (see button1 click method); the PropertyGrid has only changed the DisplayName attribute. The Description and the Category attributes are unchanged.
I would really like some help to solve this issue. Please any suggestion or solutions?
Note 1:
I don't want any responses saying that this is impossible and the attributes can only be set at design time. That is not true! This article from CodeProject.com show an example how to localize the PropertyGrid and to change the attributes in runtime. Unfortunately I have problem scoping the example for those parts I need to solve this issue.
Note 2:
I would like to avoid using resouce files. This is due to the localization is located in different language files. Each file contain a bunch of indices, each with a string value. All indices and string values are loaded into Dictionary object. To access a string the index is used to access it. I most, unfortunately, use this solution.
Best regards,
/Mc_Topaz
Here is a good article for Globalized-property-grid
You can give many resource file for the Person ,than will the propery-grid will localize.
Here is three step:
inherit from GlobalizedObject
Give the resource file for Person with the same name (eg.Person.zh-cn.resx)
change the thread's cuture which you want to display.
You can try ,Good Luck!
What you could do is reuse the DynamicTypeDescriptor class described in my answer to this question here on SO: PropertyGrid Browsable not found for entity framework created property, how to find it?
like this:
public Form1()
{
InitializeComponent();
Person p = new Person();
DynamicTypeDescriptor dt = new DynamicTypeDescriptor(typeof(Person));
propertyGrid1.SelectedObject = dt.FromComponent(p);
}
private void button1_Click(object sender, EventArgs e)
{
DynamicTypeDescriptor dt = (DynamicTypeDescriptor)propertyGrid1.SelectedObject;
DynamicTypeDescriptor.DynamicProperty dtp = (DynamicTypeDescriptor.DynamicProperty)dt.Properties["Age"];
dtp.SetDisplayName("The age");
dtp.SetDescription("Age of the person");
dtp.SetCategory("Info");
propertyGrid1.Refresh();
}
xudong125 answer solves the issue! I managed to work around the resource files solution by using a static source instead. It's to complicated to explain...
But creating classes implementing ICustomTypeDescriptor and PropertyDescriptor is the way to go.
The key was to override DisplayName, Description and Category methods in the sub class of the PropertyDescriptor class. In these overriden methods I pointed to a public static source and managed to get the strings I wanted.
/Mc_Topaz
I have a different reason to change the property description and found a fairly crude but much simpler solution for just correcting the description that is shown in the grid. Advantage for me was that the class of the object shown in the property grid required much less change.
My situation was the following: I have two boolean properties A and B, where B only can be used if A is set. If A is False, I want to make B read-only and set its description to something like "This property can only be used if you set 'A' to True". In the object code I set the Description attribute of B to this message, similar to how Mc_Topaz does that.
To only set the description that is shown for the selected property to its correct current value, I use the following SelectedGridItemChanged event handler for my PropertyGrid named pgConfig:
private void pgConfig_SelectedGridItemChanged(object sender, SelectedGridItemChangedEventArgs e)
{
GridItem giSelected = e.NewSelection;
if ((giSelected != null) && (giSelected.PropertyDescriptor != null))
{
string sDescription = GetCurrentPropertyDescription(giSelected.PropertyDescriptor.Name);
if ((sDescription != null) && (sDescription != giSelected.PropertyDescriptor.Description))
{
MethodInfo miSetStatusBox = pgConfig.GetType().GetMethod("SetStatusBox", BindingFlags.NonPublic | BindingFlags.Instance);
if (miSetStatusBox != null)
miSetStatusBox.Invoke(pgConfig, new object[] { giSelected.PropertyDescriptor.DisplayName, sDescription });
}
}
}
In the code sample, GetCurrentPropertyDescription is a private function that retrieves the current property description of the object being shown in the property grid (m_da.Config in my case):
private string GetCurrentPropertyDescription(string sPropertyName)
{
PropertyDescriptor oPropDescriptor = TypeDescriptor.GetProperties(m_da.Config.GetType())[sPropertyName];
if (oPropDescriptor != null)
{
DescriptionAttribute oDescriptionAttr = (DescriptionAttribute)oPropDescriptor.Attributes[typeof(DescriptionAttribute)];
if (oDescriptionAttr != null)
return oDescriptionAttr.Description;
}
return null;
}
My solution is less suitable than huoxudong125's if you want full globalization, but if you just want dynamic descriptions for some of your properties without changing the inheritance of the object being shown, it is an option.
Disadvantage of my method is that the underlying cached PropertyDescriptor objects of the grid are never updated, so SetStatusBox will always be called twice if a property with changed description is selected, which is inefficient.
The solution of huoxudong125 is one possible solution. I'd like to offer another one (but without talking about how to change the culture stuff at runtime - you can google that yourself ;) ). For myself I started with using localized subclasses for DisplayName, Description and Category.
As we know, DisplayName does update to current culter when PropertyGrid is updated, but Description and Category do not. I think the reason for this is on reflection level, when PropertyGrid requests category and description. As you can see, those values are cached on first read but displayName is not. To tackle that I developed two solution (where the first one is weird and I do not understand why that works myself..). Both revolve around an extra TypeDescriptionProvider
The base
First goes the custom TypeDescriptionProvider, which can be bound to any class via attribute:
internal class UpdateableGlobalizationDescriptionProvider<TTargetType> : TypeDescriptionProvider
{
private static TypeDescriptionProvider defaultTypeProvider = TypeDescriptor.GetProvider(typeof(TTargetType));
public UpdateableGlobalizationDescriptionProvider() : base(defaultTypeProvider) { }
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
{
var result = base.GetTypeDescriptor(objectType, instance);
return new ForcedGlobalizationTypeDescriptor(result);
}
}
First solution
This one revolves around "just get it done"... Add a CustomTypeDescriptor implementation, which wraps original PropertyDescriptor instances with the custom ones:
internal class ForcedGlobalizationTypeDescriptor : CustomTypeDescriptor
{
readonly ICustomTypeDescriptor inner;
public ForcedGlobalizationTypeDescriptor(ICustomTypeDescriptor typeDescriptor) : base(typeDescriptor)
{
inner = typeDescriptor;
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
// First solution
var result = base.GetProperties(attributes);
var transformed = result.OfType<PropertyDescriptor>().Select(d => new ForcedPropertyDescriptor(d)).ToArray();
return new PropertyDescriptorCollection(transformed);
}
}
and outer the PropertyDesciptor simply returns values from the wrapped PropertyDescriptor. The simplest implementation for PropertyDescriptor I found - tell me, if there is a shorter one, please.
internal class ForcedPropertyDescriptor : PropertyDescriptor
{
private PropertyDescriptor innerDescriptor;
public ForcedPropertyDescriptor(PropertyDescriptor descriptor) : base(descriptor)
{
innerDescriptor = descriptor;
}
// important:
public override string Category => base.Category;
public override string Description => base.Description;
public override Type ComponentType => innerDescriptor.ComponentType;
public override bool IsReadOnly => innerDescriptor.IsReadOnly;
public override Type PropertyType => innerDescriptor.PropertyType;
public override bool CanResetValue(object component) => innerDescriptor.CanResetValue(component);
public override object GetValue(object component) => innerDescriptor.GetValue(component);
public override void ResetValue(object component) => innerDescriptor.ResetValue(component);
public override void SetValue(object component, object value) => innerDescriptor.SetValue(component, value);
public override bool ShouldSerializeValue(object component) => innerDescriptor.ShouldSerializeValue(component);
}
I think it works, because for every read of category or description there is a new ForcedPropertyDescriptor, which has not cached the value, yet. At the same time this is a drawback: for like every request on the Category or Description propery a new instance of ForcedPropertyDescriptor is created, while Microsoft's implementation seems to cache craeated PropertyDescriptors somewehere.
Second solution
To avoid that instance creation every time, I simply stored every seen ProperyDescriptor created by ForcedGlobalizationTypeDescriptor in a set. And as soon as a localization change appears, that set gets called to reset it's items cached values:
internal class DescriptorReset
{
public static DescriptorReset Default { get; } = new DescriptorReset();
private HashSet<MemberDescriptor> descriptors = new HashSet<MemberDescriptor>();
public void Add(MemberDescriptor descriptor)
{
descriptors.Add(descriptor);
}
private void RunUpdate()
{
if (descriptors.Count == 0)
return;
FieldInfo category, description;
category = typeof(MemberDescriptor).GetField(nameof(category), BindingFlags.NonPublic | BindingFlags.Instance);
description = typeof(MemberDescriptor).GetField(nameof(description), BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var descriptor in descriptors)
{
category.SetValue(descriptor, null);
description.SetValue(descriptor, null);
}
}
}
The RunUpdate method uses Reflection to reset inner fields to null, so on the next call to corresponding properties the localized values are read again.
All you need now is some magic to call RunUpdate at the right moment. For myself I have a class in my core solution, which provides a method to set a new CultureInfo. When called, it sets the default ui culture and the default culture to the new CultureInfo and raises two events: the first one is to update all internal logic and the second one is for everything based on internal logic, like the GUI.
And since I do not know where and how long Microsoft's PropertyDescriptors are stored, I created a HashSet with WeakReference (based on WeakHashTable) to store corresponding references.
Usage
Simply append the DescriptionProvider class to your class shown in PropertyGrid:
[LocalizedDescription(nameof(MyClass), typeof(MyTextResource))]
[TypeDescriptionProvider(typeof(ForcedGlobalizationTypeDescriptor<MyClass>))]
class MyClass
{
// ...
The way your LocalizedDescription works, depends on you...
This question will probably take a while to explain, and I'll need to provide background...
This is just something I'm playing about with and isn't for production, but at the moment I have some code which looks like this:
var myDataModel = new DataModel();
myDataModel.PropertyChanged += myDataModel_PropertyChanged;
myDataModel.ChangeProperty(t => t.TestValue, 2);
So, rather than using myDataModel.TestValue = 2 directly, I'm using a ChangeProperty extension method so that I can handle all of the change events and do anything I want to in one place. My extension method looks like this (and yes, I know it's hacky):
public static class NotifyPropertyChangedExtensions
{
public static void ChangeProperty<T, U>(
this T instance,
Expression<Func<T, U>> propertyToChange,
U newValue)
{
var member = propertyToChange.Body as MemberExpression;
if (member != null)
{
if (!propertyToChange.Compile().Invoke(instance).Equals(newValue))
{
var setProperty = instance.GetType().GetProperty(
member.Member.Name,
BindingFlags.SetProperty |
BindingFlags.Public |
BindingFlags.Instance);
if (setProperty != null)
{
// actually set the property
setProperty.SetValue(instance, newValue, null);
// raise the property changed event
if (typeof(INotifyPropertyChanged).IsAssignableFrom(
typeof(T)))
{
var delegatesToCall =
instance.GetType().GetField("PropertyChanged",
BindingFlags.Instance |
BindingFlags.NonPublic)
.GetValue(instance) as MulticastDelegate;
if (delegatesToCall != null)
{
var eventArgs = new PropertyChangedEventArgs(
setProperty.Name);
foreach (var #delegate in
delegatesToCall.GetInvocationList())
{
#delegate.Method.Invoke(
#delegate.Target,
new object[] { instance, eventArgs });
}
}
}
}
}
}
else
{
throw new ArgumentException(
string.Format(
"Cannot determine the property to change {0}",
propertyToChange));
}
}
}
With this architecture, my data model is quite clean:
public class DataModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int TestValue { get; set; }
}
That is, I can use auto-properties, and don't need to worry about raising events etc.
Now, what I actually want to do is something closer to this:
var dataModel = new DataModel();
myDataModel.PropertyChanged += myDataModel_PropertyChanged;
myDataModel.TestValue.Set(2); // this is what I want...
So, I'm thinking I'll basically need an extension method - but I can only see how to send the property itself (the TestValue in this case), and the new value. So then I wondered if it's possible, given a property, to find out the instance of the class it belongs to?
Don't do this. It breaks encapsulation.
No. In myDataModel.TestValue.Set(2); the extension method will always be called on the value returned by the property. There is no way to get the class, instance or property that returned the value.
You could do something like this:
var t = new DataModel();
((Expression<Func<int>>)(() => t.Foo)).Set(100);
with
static class Extensions
{
public static void Set<T>(this Expression<Func<T>> expression, T value)
{ ... }
}
but this is ugly, almost unreadable, unclear, inefficient, and error prone.
You're looking for Aspect Oriented Programming (AOP).
Have a look at PostSharp or LinFu.
There is no really clean solution to implementing INotifyPropertyChanged yet. If typing all the property setters is too much work or too error prone, I'd generate them with a T4 template.