Как реализовать ISerializable в F#
Допустим, вы начинаете с этой заглушки:
[<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
Проблема заключается в том, что спецификация гласит: "В общем, этот конструктор должен быть защищен, если класс не запечатан". F# не имеет защищенного ключевого слова - так как мне это сделать?
Ограничения (из-за требования полностью соответствовать существующим классам C# на уровне API):
- Необходимо реализовать ISerializable
- Конструктор должен быть защищен
РЕДАКТИРОВАТЬ - интересная дополнительная информация Спецификация F# говорит, что если вы переопределите защищенную функцию, то полученная функция будет защищена. Это неверно Если вы не укажете доступность, результирующее переопределение будет публичным, несмотря ни на что (нарушение контракта).
3 ответа
В настоящее время невозможно сделать это, используя язык как есть. Это можно сделать, и у меня есть два пути.
Во-первых, запустить выходную сборку через ILDASM, выполнить регулярное выражение в объявлении метода, которое вы хотите, изменить "public" на "family" в нужном вам методе, а затем ILASM обратно. Ewwwww.
Второе, что я исследую, это помечать методы
[<Protected>]
затем напишите фильтр с CCI, чтобы изменить доступность для всех методов, кроме ProtectedAttribute, а затем удалите атрибут. Это выглядит менее неприлично, чем запуск регулярного выражения для файла, но мои настройки безопасности на работе серьезно ненавидят исходный код проекта CCI, поэтому я не могу успешно извлечь / распаковать / собрать его.
РЕДАКТИРОВАТЬ - Вот мое решение - я попробовал CCI, но он не готов к задаче. В итоге я использовал Сесил и получил следующий код:
Сначала атрибут в F#
открытая система
[<AttributeUsage(AttributeTargets.Method ||| AttributeTargets.Constructor, AllowMultiple=false, Inherited=true)>]
type MyProtectedAttribute() =
inherit System.Attribute()
тогда следующее приложение, которое является клиентом Сесила:
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;
}
}
}
наконец, украсьте методы, которые вы хотите защитить, с помощью MyProtectedAttribute и запустите приложение C# в качестве шага после сборки.
К сожалению, нет никакого способа - F# не имеет защищенных членов. Мы рассмотрим это в следующих версиях.
На самом деле защищенный модификатор - это не принуждение, а рекомендация
Во время десериализации SerializationInfo передается в класс с помощью конструктора, предоставленного для этой цели. Любые ограничения видимости, наложенные на конструктор, игнорируются при десериализации объекта; так что вы можете пометить класс как открытый, защищенный, внутренний или закрытый.
Так что это должно работать:
[<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}
*)