views:

103

answers:

3

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):

  1. Must implement ISerializable
  2. 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).

+1  A: 

Unfortunately, there is no way - F# does not have protected members. We will consider this in future versions.

Mitya
+1  A: 

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}
*)
desco
Of course, but it misses my constraint of matching an existing API exactly.
plinth
+1  A: 

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.

plinth