patterncsharpMinor
Drying Up Very Similar Implementations of an Interface
Viewed 0 times
implementationsdryinginterfaceverysimilar
Problem
Work continues on the Rubberduck VBA Editor Add-in. I have a need to call some VBA code from the add-in with
Which is great, because now I can call VBA's
The problem is my implementations of
ExcelApp:
```
[ComVisible(false)]
public class ExcelApp : IHostApplication
{
Excel.Application _application;
public ExcelApp()
{
_application = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
}
~ExcelApp()
{
Marshal.ReleaseComObject(_application);
}
public void Run(string target)
{
_application.Run(target);
}
/// Timed call to Application.Run
///
/// Name of the project containing the method to be run.
/// Name of the module containing the method to be run.
/// Name of the method run.
///
/// Number of milliseconds it took to run the VBA procedure.
public long TimedMethodCall(string projectName, string moduleName, string methodName)
{
var stopwatch = Stopwatch.StartNew();
/
Application.Run. Unfortunately, the Application part of that refers to the particular Office Application that is hosting the VBA editor. So, if I'm working in Excel, Application refers to the instance of Excel (Word, Access, etc.). Unfortunately, these do not share a common interface, so I wrapped them up in one of my own so I could use them interchangeably. [ComVisible(false)]
public interface IHostApplication
{
void Run(string target);
long TimedMethodCall(string projectName, string moduleName, string methodName);
}Which is great, because now I can call VBA's
Application.Run method without caring which Office Application is hosting the VBA Editor. duration =_hostApp.TimedMethodCall( _projectName, _moduleName, _methodName);The problem is my implementations of
IHostApplication are all very similar. Like, copy/paste similar. I considered a base class, but don't see how that could work with all of the Application objects being a different type. Maybe some delegate "magic" would help me out here??ExcelApp:
```
[ComVisible(false)]
public class ExcelApp : IHostApplication
{
Excel.Application _application;
public ExcelApp()
{
_application = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
}
~ExcelApp()
{
Marshal.ReleaseComObject(_application);
}
public void Run(string target)
{
_application.Run(target);
}
/// Timed call to Application.Run
///
/// Name of the project containing the method to be run.
/// Name of the module containing the method to be run.
/// Name of the method run.
///
/// Number of milliseconds it took to run the VBA procedure.
public long TimedMethodCall(string projectName, string moduleName, string methodName)
{
var stopwatch = Stopwatch.StartNew();
/
Solution
You could consider using an generic abstract base class.
The Excel application would then be implemented as follows:
You could get rid of the abstract methods by using
(As a side-note: Scala would offer you structural typing in that case, which AFAIK will use reflection internally.)
[ComVisible(false)]
public abstract class HostApplicationBase : IHostApplication
{
protected readonly TApplication _application;
protected HostApplicationBase(string applicationName)
{
_application = (TApplication)Marshal.GetActiveObject(applicationName + ".Application");
}
~HostApplicationBase()
{
Marshal.ReleaseComObject(_application);
}
public abstract void Run(string target);
protected abstract string GenerateFullyQualifiedName(string projectName, string moduleName, string methodName);
public long TimedMethodCall(string projectName, string moduleName, string methodName)
{
var stopwatch = Stopwatch.StartNew();
Run(GenerateFullyQualifiedName(projectName, moduleName, methodName));
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}The Excel application would then be implemented as follows:
[ComVisible(false)]
public class ExcelApp : HostApplicationBase
{
public ExcelApp() : base("Excel") { }
public override void Run(string target)
{
base._application.Run(target);
}
protected override string GenerateFullyQualifiedName(string projectName, string moduleName, string methodName)
{
return string.Concat(projectName, ".", moduleName, ".", methodName);
}
}You could get rid of the abstract methods by using
Actions, but there is really no great difference. Now only the Run-method will look the same for all applications. You could get rid of this using reflection.(As a side-note: Scala would offer you structural typing in that case, which AFAIK will use reflection internally.)
Code Snippets
[ComVisible(false)]
public abstract class HostApplicationBase<TApplication> : IHostApplication
{
protected readonly TApplication _application;
protected HostApplicationBase(string applicationName)
{
_application = (TApplication)Marshal.GetActiveObject(applicationName + ".Application");
}
~HostApplicationBase()
{
Marshal.ReleaseComObject(_application);
}
public abstract void Run(string target);
protected abstract string GenerateFullyQualifiedName(string projectName, string moduleName, string methodName);
public long TimedMethodCall(string projectName, string moduleName, string methodName)
{
var stopwatch = Stopwatch.StartNew();
Run(GenerateFullyQualifiedName(projectName, moduleName, methodName));
stopwatch.Stop();
return stopwatch.ElapsedMilliseconds;
}
}[ComVisible(false)]
public class ExcelApp : HostApplicationBase<Excel.Application>
{
public ExcelApp() : base("Excel") { }
public override void Run(string target)
{
base._application.Run(target);
}
protected override string GenerateFullyQualifiedName(string projectName, string moduleName, string methodName)
{
return string.Concat(projectName, ".", moduleName, ".", methodName);
}
}Context
StackExchange Code Review Q#72290, answer score: 6
Revisions (0)
No revisions yet.