Let's say you start off with this stub:
[<Serializable>]
type Bounderizer =
val mutable _boundRect : Rectangle
new (boundRect : Rectangle) = { _boundRect = boundRect ; }
new () = { _boundRect = Rectangle(0, 0, 1, 1); }
new (info:SerializationInfo, context:StreamingContext) =
{ // to do
}
interface ISerializable with
member this.GetObjectData(info, context) =
if info = null then raise(ArgumentNullException("info"))
info.AddValue("BoundRect", this._boundRect)
// TODO - add BoundRect property
The issue is that the spec says, "In general, this constructor should be protected if the class is not sealed." F# doesn't have a protected keyword - so how do I do this?
Constraints (due to a requirement to match perfectly existing C# classes at the API level):
- Must implement ISerializable
- Constructor must be protected
EDIT - interesting extra information
The F# spec says that if you override a protected function that the resulting function will be protected. This is incorrect. If you don't specify accessibility, the resulting override will be public no matter what (breaking the contract).
It is not possible to do this currently using the language as is. It is possible to do this and I have two ways.
The first is to run the output assembly through ILDASM, regex to the method declaration you want, change 'public' to 'family' in the method you want, then ILASM it back. Ewwwww.
The second, which I'm investigating, is to tag the methods with
[<Protected>]
then write a filter with CCI to change the accessibility on all methods than have the ProtectedAttribute and then remove the attribute. This seems less unseemly than running a regex over the file, but my security settings at work seriously hates the CCI project source, so I can't successfully fetch/decompress/built it.
EDIT - Here is my solution - I tried CCI, but it's not ready for the task. I ended up using Cecil and ended up with the following code:
First an attribute in F#
open System
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Constructor, AllowMultiple=false, Inherited=true)>]
type MyProtectedAttribute() =
inherit System.Attribute()
then the following app which is a client of Cecil:
using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Collections.Generic;
using System.IO;
namespace AddProtectedAttribute
{
class Program
{
static void Main(string[] args)
{
if (args.Length != 1 || args.Length != 3)
{
Console.Error.WriteLine("Usage: AddProtectedAttribute assembly-file.dll /output output-file.dll");
return;
}
string outputFile = args.Length == 3 ? args[2] : null;
ModuleDefinition module = null;
try
{
module = ModuleDefinition.ReadModule(args[0]);
}
catch (Exception err)
{
Console.Error.WriteLine("Unable to read assembly " + args[0] + ": " + err.Message);
return;
}
foreach (TypeDefinition type in module.Types)
{
foreach (MethodDefinition method in type.Methods)
{
int attrIndex = attributeIndex(method.CustomAttributes);
if (attrIndex < 0)
continue;
method.CustomAttributes.RemoveAt(attrIndex);
if (method.IsPublic)
method.IsPublic = false;
if (method.IsPrivate)
method.IsPrivate = false;
method.IsFamily = true;
}
}
if (outputFile != null)
{
try
{
module.Write(outputFile);
}
catch (Exception err)
{
Console.Error.WriteLine("Unable to write to output file " + outputFile + ": " + err.Message);
return;
}
}
else
{
outputFile = Path.GetTempFileName();
try
{
module.Write(outputFile);
}
catch (Exception err)
{
Console.Error.WriteLine("Unable to write to output file " + outputFile + ": " + err.Message);
if (File.Exists(outputFile))
File.Delete(outputFile);
return;
}
try
{
File.Copy(outputFile, args[0]);
}
catch (Exception err)
{
Console.Error.WriteLine("Unable to copy over original file " + outputFile + ": " + err.Message);
return;
}
finally
{
if (File.Exists(outputFile))
File.Delete(outputFile);
}
}
}
static int attributeIndex(Collection<CustomAttribute> coll)
{
if (coll == null)
return -1;
for (int i = 0; i < coll.Count; i++)
{
CustomAttribute attr = coll[i];
if (attr.AttributeType.Name == "MyProtectedAttribute")
return i;
}
return -1;
}
}
}
finally, decorate the methods you want to be protected with MyProtectedAttribute and run the C# app as a post-build step.
Unfortunately, there is no way - F# does not have protected members. We will consider this in future versions.
in fact protected modifier is not enforcement, but a recommendation
During deserialization, SerializationInfo is passed to the class using the constructor provided for this purpose. Any visibility constraints placed on the constructor are ignored when the object is deserialized; so you can mark the class as public, protected, internal, or private.
So this should work:
[<Serializable>]
type Bounderizer =
val mutable _boundRect : Rectangle
new (boundRect : Rectangle) = { _boundRect = boundRect ; }
new () = { _boundRect = Rectangle(0, 0, 1, 1); }
private new (info:SerializationInfo, context:StreamingContext) =
Bounderizer(info.GetValue("BoundRect", typeof<Rectangle>) :?> Rectangle)
then
printfn "serialization ctor"
interface ISerializable with
member this.GetObjectData(info, context) =
if info = null then raise(ArgumentNullException("info"))
info.AddValue("BoundRect", this._boundRect)
override this.ToString() = this._boundRect.ToString()
let x = Bounderizer(Rectangle(10, 10, 50, 50))
let ms = new MemoryStream()
let f = new BinaryFormatter()
f.Serialize(ms, x)
ms.Position <- 0L
let y = f.Deserialize(ms) :?> Bounderizer
printfn "%O" y
(*
serialization ctor
{X=10,Y=10,Width=50,Height=50}
*)