Using a Proxy Class to Fix F# Protected Access Limitation
In this previous entry, I presented a workaround to F# not having support for protected members. Upon thinking on it further, I decided that this could nearly be fixed with an adapter class. The adapter class would be implemented in C# and would override all protected members and call optional/non-optional delegates to perform the actual work.
If you wanted to implement an ImageCommand from dotImage in F#, you would start with a C# class like this:
using System;
using System.Drawing;
using Atalasoft.Imaging;
using Atalasoft.Imaging.ImageProcessing;
using System.Runtime.Serialization;
namespace DelegatedImageCommander
{
public delegate AtalaImage PerformActualCommandDelegate(AtalaImage source, AtalaImage dest, Rectangle area, ref ImageResults results);
public abstract class DelegatedImageCommand : ImageCommand
{
Func<AtalaImage, AtalaImage> _constructChangedSourceImage, _constructFinalImage;
Func<ImageResults> _constructImageResults;
Func<AtalaImage, PixelFormat, AtalaImage> _getChangedPixelFormat;
PerformActualCommandDelegate _performActualCommand;
Func<AtalaImage, PixelFormat, PixelFormat[], PixelFormat> _selectBestAlternatePixelFormat, _selectPreferredPixelFormat;
Action<AtalaImage> _verifyProperties;
protected DelegatedImageCommand(PerformActualCommandDelegate performActualCommand,
Action<AtalaImage> verifyProperties,
Func<AtalaImage, AtalaImage> constructChangedSourceImage,
Func<AtalaImage, AtalaImage> constructFinalImage,
Func<ImageResults> constructImageResults,
Func<AtalaImage, PixelFormat, AtalaImage> getChangedPixelFormat,
Func<AtalaImage, PixelFormat, PixelFormat[], PixelFormat> selectBestAlternatePixelFormat,
Func<AtalaImage, PixelFormat, PixelFormat[], PixelFormat> selectPreferredPixelFormat)
{
if (performActualCommand == null)
throw new ArgumentNullException("performActualCommand");
_performActualCommand = performActualCommand;
_verifyProperties = verifyProperties;
_constructChangedSourceImage = constructChangedSourceImage;
_constructFinalImage = constructFinalImage;
_constructImageResults = constructImageResults;
_getChangedPixelFormat = getChangedPixelFormat;
_selectBestAlternatePixelFormat = selectBestAlternatePixelFormat;
_selectPreferredPixelFormat = selectPreferredPixelFormat;
}
protected DelegatedImageCommand(PerformActualCommandDelegate performActualCommand, Action<AtalaImage> verifyProperties)
: this(performActualCommand, verifyProperties, null, null, null, null, null, null)
{
}
protected override AtalaImage ConstructChangedSourceImage(AtalaImage image)
{
if (_constructChangedSourceImage != null)
return _constructChangedSourceImage(image);
return base.ConstructChangedSourceImage(image);
}
protected override AtalaImage ConstructFinalImage(AtalaImage image)
{
if (_constructFinalImage != null)
return _constructFinalImage(image);
return base.ConstructFinalImage(image);
}
protected override ImageResults ConstructImageResults()
{
if (_constructImageResults != null)
return _constructImageResults();
return base.ConstructImageResults();
}
protected override AtalaImage GetChangedPixelFormat(AtalaImage sourceImage, PixelFormat newFormat)
{
if (_getChangedPixelFormat != null)
return _getChangedPixelFormat(sourceImage, newFormat);
return base.GetChangedPixelFormat(sourceImage, newFormat);
}
protected override AtalaImage PerformActualCommand(AtalaImage source, AtalaImage dest, Rectangle imageArea, ref ImageResults results)
{
return _performActualCommand(source, dest, imageArea, ref results);
}
protected override PixelFormat SelectBestAlternatePixelFormat(AtalaImage sourceImage, PixelFormat sourceFormat, PixelFormat[] formats)
{
if (_selectBestAlternatePixelFormat != null)
return _selectBestAlternatePixelFormat(sourceImage, sourceFormat, formats);
return base.SelectBestAlternatePixelFormat(sourceImage, sourceFormat, formats);
}
protected override PixelFormat SelectPreferredPixelFormat(AtalaImage sourceImage, PixelFormat sourceFormat, PixelFormat[] formats)
{
if (_selectPreferredPixelFormat != null)
return _selectPreferredPixelFormat(sourceImage, sourceFormat, formats);
return base.SelectPreferredPixelFormat(sourceImage, sourceFormat, formats);
}
protected override void VerifyProperties(AtalaImage image)
{
if (_verifyProperties != null)
_verifyProperties(image);
}
}
}
Then in F#, instead of inheriting from ImageCommand, you would inherit from DelegatedImageCommand and provide a set of delegates to perform the actual work. In ImageCommand, you can get away with default behavior for everything except PerformActualCommand and you’re good to go. I provided two constructors: one with all the delegates and one with the most common case. Now an F# class that inherits from DelegatedImageCommand will present the correct interface to the world. Almost.
I say almost because ImageCommand is serializable, and as such you are supposed to create a constructor that is protected which takes a SerializationInfo object and a StreamingContext object. F# doesn’t let you make protected constructors, so this approach won’t work properly. It turns out that the protected constructor can be public – it is only suggested that it is protected.
However, if you want to do this properly, you would need to define a shell class in C# that has the correct public interface and data/property model and then do the actual work in F#.