编译后将属性注入到 .NET 类中
我想实现 WPF MVVM 模式的 ViewModel 部分,而不引用 WPF 程序集。 有问题的部分是命令路由,它要求 ViewModel 实现 ICommand 类型的属性,以便命令绑定可以工作。
现在,我可以避免使用 ICommand
并简单地将属性声明为 object
。 一切仍然有效,仅此而已。 但令我困扰的是,我仍然必须声明它们,而且我真的不想这样做,因为它们感觉就像样板代码。
我的 ViewModel 目前如下所示:
public class HelloWorldViewModel : ViewModel
{
[BoundProperty]
public string Name { get; set; }
[CommandHandler("SayHello")]
public bool CanSayHello()
{
return Name != "" && Name != null;
}
[CommandHandler("SayHello")]
public void SayHello()
{
View.ShowMessage("Hello, {0}!", Name);
}
public object SayHello { get; private set; }
}
CommandHandlerAttribute
启用命令处理程序的运行时发现(Action
和可选的 Func
),而BoundPropertyAttribute
实际上是一个方面,它将自身注入属性设置器并调用INotifyPropertyChanged
。 我通过使用编译时 IL 编织器来实现这一点。
理想情况下,我也希望将最后一行(SayHello 属性)设为隐式。 如果不是 WPF 的要求,那么将其放在源代码中就没有意义。
因此,很自然地,我正在考虑使用 CommandHandlerAttribute 方面将必要的 IL 注入到类中,并本质上创建编译后属性。 这是相当困难的,尽管一个好的 IL 编织器(例如 PostSharp)可以在很大程度上使它变得更容易。
在踏上这段旅程之前,我想听听大家对我的方法的看法。 声音好吗? 有没有更好的办法? 你会/怎么做?
I'd like to implement the ViewModel part of WPF's MVVM pattern without referencing WPF assemblies. The problematic part is command routing, which requires that ViewModels implement properties of type ICommand
so that command bindings can work.
Now, I can avoid the ICommand
and simply declare the properties as object
. Everything still works, so that's that. But what bothers me is, I still have to declare them, and I really don't want to, because they feel like boiler plate code.
My ViewModels currently look like this:
public class HelloWorldViewModel : ViewModel
{
[BoundProperty]
public string Name { get; set; }
[CommandHandler("SayHello")]
public bool CanSayHello()
{
return Name != "" && Name != null;
}
[CommandHandler("SayHello")]
public void SayHello()
{
View.ShowMessage("Hello, {0}!", Name);
}
public object SayHello { get; private set; }
}
The CommandHandlerAttribute
enables runtime discovery of command handlers (an Action
and an optional Func<bool>
), while the BoundPropertyAttribute
is really an aspect that injects itself into the property setter and calls INotifyPropertyChanged
. I accompish this by using a compile time IL weaver.
Ideally, I'd like to make the last line (the SayHello property) implicit, too. There would be no point in having it there in the source if it wasn't for WPF's requirement.
So, naturally, I'm thinking of using the CommandHandlerAttribute
aspect to inject the necessary IL into class and essentially creating the property post-compile. This is quite hard, although a good IL weaver (such as PostSharp) can go a long way to make it easier.
Before I embark on this journey, I'd like to hear what you all think of my approach. Is it sound? Is there a better way? How would/do you do it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
对我来说,这听起来太聪明了。 发生了太多“魔法”。 特别是,我不喜欢 CommandHandlerAttribute 的魔法字符串和其他方面。 也就是说,如果我要走这条路,我会使用类似于 EventAggregator 的东西,但用于命令。 IOW,SayHello 根本不会存在于您的 ViewModel 上。 无论什么魔法创建到 SayHell() 和 CanSayHello() 的命令绑定都会在全局 CommandAggregator 中找到该命令。 只要我们为此使用魔术字符串,CommandAggregator 中的命令就可以延迟创建,因此不需要您进行“样板”编码。 剩下的就是创建一些 XAML 魔法(标记扩展)来指定 ICommandSource 上的命令。
To me this sounds too clever by far. There's too much "magic" happening. In particular, I dislike the magic strings and other aspects of your CommandHandlerAttribute. That said, if I were to go down this route, I'd use something akin to the EventAggregator but for commands. IOW, SayHello wouldn't exist on your ViewModel at all. What ever magic creates the command bindings to SayHell() and CanSayHello() would instead locate the command in the global CommandAggregator. As long as we're using magic strings for this, the commands in the CommandAggregator could be lazily created, thus requiring no "boiler plate" coding on your part. All that's left is to create some XAML magic (markup extension) to specify the command on the ICommandSource.
我建议你看看这是如何实现的,它会有所帮助:
“Kind Of Magic”
轻松的 INotifyPropertyChanged
[http://visualstudiogallery.msdn.microsoft.com/d5cd6aa1-57a5-4aaa-a2be-969c6db7f88a][1]
作为将其添加到一个属性的示例:
将其添加到所有类属性的另一个示例:
i advice you to see how this was implemented, and it will help:
"Kind Of Magic"
Effortless INotifyPropertyChanged
[http://visualstudiogallery.msdn.microsoft.com/d5cd6aa1-57a5-4aaa-a2be-969c6db7f88a][1]
as an example for adding it to one property:
Another example for adding it to all the class properties:
在使用 Prism 一段时间后,但在我开始了解 MVVM 的东西之前,我想出了一个我仍然认为具有一定有效性的策略:
我创建了一个基于反射的 ICommand 接口的实现。 构造函数接受目标对象和操作名称。 使用反射,代码查找名称为“[操作]”的方法、名称为“Can[操作]”或“[操作]Enabled”的属性或方法以及名称为“Can[操作]Changed”或“[操作]启用已更改”。 仅需要第一个,但反射的方法/属性/事件已连接到 ICommand 接口的相当基本的实现。
然后,我创建了 IValueConverter 的实现,它将创建前一个类的实例,传递要转换的值作为目标对象,并将转换器的参数作为操作名称。
有了上述组件,我就能够将按钮的 Command 属性直接绑定到操作源(以及指定转换器),并将按钮的 CommandParameter 属性设置为操作的名称。 通过这种方式,我获得了声明性命令绑定,而命令源对任何 WPF 都没有了解。
Some time after playing with Prism, but before I'd seem the MVVM stuff, I came up with a strategy that I still think has some validity:
I created an implementation of the ICommand interface based on reflection. The constructor accepted a target object and an operation name. Using reflection, the code looked for a method with name "[operation]", property or method with name "Can[operation]" or "[operation]Enabled" and an event with name "Can[operation]Changed" or "[operation]Enabled Changed". Only the first was required, but the reflected method/property/event were wired up to a pretty basic implementation of the ICommand interface.
I then created an implementation of IValueConverter that would create an instance of the previous class, passing the value to be converted as the target object, and the parameter of the converter being the operation name.
Given the above components, I was then able to, for example, bind a button's Command property directly to the source of the operation (along with specifying the converter), and set the Button's CommandParameter property to the name of the operation. In this way, I got declarative command binding without the command source having carnal knowledge of anything WPF.
我个人的观点是,这很有趣,但我一般会避免它。
避免样板代码(或感觉像样板代码的代码)会产生后果。 这似乎是一个好主意,因为您不需要不断地重新输入内容,但从长远来看,您会降低它的可读性和可理解性。
就我个人而言,我尝试设置良好的代码模板来为我插入样板代码,并将其包装在区域中,以便我可以将其隐藏在源代码中。 在这种情况下,用样板文件填充文件所花费的 30 秒(对我来说)比两年后我试图理解代码时所花费的 2 小时要痛苦,或者更糟糕的是,某人的两周其他人花了两年时间才试图理解我的代码......
My personal opinion is that this is interesting, but I would avoid it in general.
Avoiding boiler-plate code (or code that feels like boiler plate code) has consequences. It may seem like a good idea, since you're not retyping things constantly, but in the long run, you're making it less readable and understandable.
Personally, I try to just setup good code templates to insert the boiler plate code for me, and wrap it in regions so I can hide it in the source code. The 30 seconds it takes to fill in a file with boiler plate in that case is less painful (for me) than the 2 hours I spend, two years later when I'm trying to understand the code, or worse, the two weeks somebody else spends two years later when they're trying to understand my code....
我认为,针对您的情况,最好的方法是代理或装饰器模式。 您可以在运行时使用 UI/WPF 内容成员包装/装饰的低级实体。 这是最简单但有效的方法,可以节省您的时间,并且不用担心框架、注入等。
唯一的事情是您必须设计一些小型基础设施来用适当的装饰器包装您的实体。
The best way in your case is Proxy or Decorator pattern I think. You can low level entities that are wrapped/decorated with UI/WPF stuff members during runtime. This is the simplest but yet efficient way to save your time and don't bother with frameworks, injections, etc.
The only thing is you will have to design some small infrastructure to wrap your entities with appropriate decorators.