Introduction
The good programmer is a lazy programmer - that is a reason for DRY principle. At least it's how I understand it.
While coding one rather complex View Model I've noticed many declarations of ICommand
public properties backed with the private field. Moreover for most of the commands two methods are declared;
private void SaveCmd(object param){...};
private bool CanSaveCmd(object param){...};
That gives the pattern for convention:
private void <CommandName><Cmd|CmdManual>(object param){...};
private bool Can<CommandName><Cmd|CmdManual>(object param){...};
It will become clear a bit later why there is a choice between two options Cmd, and CmdManual.
Implementation
The idea is simple. Now we should look for the ways of its implementation. Two variants come to mind.
- Support of dynamic interface (
IDynamicMetaObjectProvider
) - Creating commands and exposing them through the dictionary when View Model is instantiated.
I'm not experienced in DLR and have concerns about performance of the former approach that's why I chose the latter one. Fortunately WPF XAML supports indexers and we can bind commands like this:
<Button Command="{Binding Commands[SaveAs]}"/>
Where Commands
is a readonly dictionary property in ViewModelBase
class:
private readonly Dictionary<string, IExtendedCommand> _commands =
new Dictionary<string,IExtendedCommand>(StringComparer.InvariantCultureIgnoreCase);
private readonly IReadOnlyDictionary<string, IExtendedCommand> _readOnlyCommands;
public IReadOnlyDictionary<string, IExtendedCommand> Commands
{
get { return _readOnlyCommands; }
}
Here I declared IExtendedCommand
interface for two purposes:
public interface IExtendedCommand : ICommand
{
string Name { get; }
void RaiseCanExecuteChanged();
}
Name property is useful for debugging|tracing and RaiseCanExecuteChanged()
allows to notify about command's state change. By default in our CommandBase
class CanExecuteChanged
event is delegated to static CommandManager.RequerySuggested
event what eliminates the necessity of caring about well-judged notifications. But this has a downside: RequerySuggested
fires two often and can be the cause of performance degradation if CanExecute
requires a lot time to compute. Therefore we need to select mode between automatic to manual raise of CanExecuteChanged
event. CmdManual postfix serves exactly for this purpose.
Here is the code of CommandBase
class:
public sealed class CommandBase : IExtendedCommand
{
private readonly bool _doNotUseCommandManager;
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
private readonly string _name;
public string Name
{
get { return _name; }
}
public CommandBase(Action<object> execute) : this(execute, null)
{
}
public CommandBase(Action<object> execute, Predicate<object> canExecute,
string name = null, bool doNotUseCommandManager = false)
{
_execute = execute;
_canExecute = canExecute;
_name = name;
_doNotUseCommandManager = doNotUseCommandManager;
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
Debug.WriteLine("CanExecute value requested in Command {0}", new object[]{ _name });
#if DEBUG
var sw = Stopwatch.StartNew();
#endif
var result = _canExecute == null || _canExecute(parameter);
#if DEBUG
sw.Stop();
Debug.WriteLine("CanExecute took {0} ms to complete.", sw.ElapsedMilliseconds);
#endif
return result;
}
private EventHandler _canExecuteChanged;
public event EventHandler CanExecuteChanged
{
add
{
_canExecuteChanged += value;
if (!_doNotUseCommandManager)
{
CommandManager.RequerySuggested += value;
}
Debug.WriteLine("CanExecuteChanged listener attached to Comamnd {0}", new object[] { _name });
}
remove
{
_canExecuteChanged -= value;
if (!_doNotUseCommandManager)
{
CommandManager.RequerySuggested -= value;
}
Debug.WriteLine("CanExecuteChanged listener detached from Comamnd {0}", new object[] { _name });
}
}
public void RaiseCanExecuteChanged()
{
_canExecuteChanged.Raise(this);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
and Raise
is the extension method for thread-safety:
public static class EventHelper
{
public static void Raise(this EventHandler handler,object sender)
{
if (handler != null)
handler(sender, EventArgs.Empty);
}
public static void Raise<TArgs>(this EventHandler<TArgs> handler, object sender, TArgs args)
where TArgs : EventArgs
{
if (handler != null)
handler(sender, args);
}
public static void Raise(this PropertyChangedEventHandler handler,
object sender, PropertyChangedEventArgs args)
{
if (handler != null)
handler(sender, args);
}
public static void Raise(this PropertyChangingEventHandler handler, object sender, PropertyChangingEventArgs args)
{
if (handler != null)
handler(sender, args);
}
public static void Raise(this NotifyCollectionChangedEventHandler handler,
object sender, NotifyCollectionChangedEventArgs args)
{
if (handler != null)
handler(sender, args);
}
}
Note, please, that add
and remove
of CanExecuteChanged
implemented in a not thread-safe manner!
Next step is to implement ad hoc DI for creating instances of commands:
public interface ICommandBuilder
{
IExtendedCommand BuildCommand(Action<object> execute,
Predicate<object> canExecute, string name, bool doNotUseCommandManager);
}
public interface ICommandsProvider
{
void FillCommands(ViewModelBase viewModel, Dictionary<string, IExtendedCommand> commands);
}
and in ViewModelBase
class:
private static ICommandBuilder _commandBuilder;
public static ICommandBuilder CommandBuilder
{
get { return _commandBuilder; }
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_commandBuilder = value;
}
}
public static readonly List<ICommandsProvider>
CommandProviders = new List<ICommandsProvider>();
static ViewModelBase()
{
CommandBuilder = new DefaultCommandBuilder();
CommandProviders.Add(new DefaultCommandsProvider());
}
DefaultCommandBuilder
is nothing more than a wrapper of CommandBase
constructor:
public class DefaultCommandBuilder : ICommandBuilder
{
public IExtendedCommand BuildCommand(Action<object> execute,
Predicate<object> canExecute, string name, bool doNotUseCommandManager)
{
return new CommandBase(execute, canExecute, name, doNotUseCommandManager);
}
}
Default Commands Provider
Eventually we got to the essence of the article DefaultCommandsProviderClass
. Indeed it is rather simple class. It scans the type hierarchy (up to ViewModelBase
) for non public (protected or private) nongeneric methods matching the pattern discussed in Introduction. Then for each execute candidate it tries to find can execute candidate throwing ViewModelCommandException
if these candidates have different postfixes or if number of can execute candidates is greater than number of execute candidates.
As candidates are searched in the first step it is possible for execute to be a method of ancestor and can execute - method of descendant. It's up to you to treat it either as a bug or a feature.
private static CommandPart GetCommandPart(MethodInfo mi)
{
var matchedPostfix = CommandPostfixes.First(commandPosfix =>
mi.Name.EndsWith(commandPosfix, true, CultureInfo.InvariantCulture));
Debug.WriteLine("Matched postfix {0}", new object[]{matchedPostfix});
var nameWithoutPostfix = mi.Name.Remove(mi.Name.Length - matchedPostfix.Length);
if (nameWithoutPostfix.Length > 0)
{
var doNotUseCommandManager = string.Compare(matchedPostfix,
ManualCanExecuteChangedRaisePostfix, true, CultureInfo.InvariantCulture) == 0;
var cmdInfo = new CommandPart
{
Method = mi,
DoNotUseCommandManager = doNotUseCommandManager
};
string commandName;
if (mi.ReturnType == typeof(bool) &&
nameWithoutPostfix.StartsWith(CanExecutePrefix, true, CultureInfo.InvariantCulture)
&& nameWithoutPostfix.Length > CanExecutePrefix.Length)
{
commandName = nameWithoutPostfix.Substring(CanExecutePrefix.Length);
cmdInfo.Type = CommandPart.MethodType.CanExecute;
}
else if (mi.ReturnType == typeof(void) &&
!nameWithoutPostfix.StartsWith(CanExecutePrefix, true, CultureInfo.InvariantCulture))
{
commandName = nameWithoutPostfix;
cmdInfo.Type = CommandPart.MethodType.Execute;
}
else
{
Debug.WriteLine("Resulting command name is invalid. Method name is {0}.", new object[] { mi.Name });
return null;
}
cmdInfo.Name = commandName;
return cmdInfo;
}
Debug.WriteLine("Resulting command name is invalid. Method name is {0}", new object[] { mi.Name });
return null;
}
private class CommandPart
{
public enum MethodType { Unknown, Execute, CanExecute }
public MethodType Type { get; set; }
public string Name { get; set; }
public MethodInfo Method { get; set; }
public bool DoNotUseCommandManager { get; set; }
public CommandPart()
{
Type = MethodType.Unknown;
}
}
The whole algorithm of FillCommands()
is trivial:
- Get candidates in form of
CommandPart
- Split collection by Type:
Execute
or CanExecute
- Foreach Execute part try to find
CanExecute
part - Create corresponding delegates
- Ask builder for new command
- Add command to the dictionary
That's it. From this point methods conventionally become commands.
But... I'd like to point out this approach has a couple of disadvantages: Everything comes at a cost and such loose coupling makes impossible IntelliSense support in XAML designer. Moreover if you use ReSharper and declare methods as private they will be grayed as never used in the code.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.