我已经接受了 Adolfo Perez 的回答,因为正是他的想法使我找到了一个可行的解决方案,但我在这里发布了一些关于我所做的事情的详细信息,以供其他可能试图实现类似目标的人使用。
Adolfo 建议将有问题的属性的类型从string
更改为object
,希望它可以欺骗 XAML 序列化过程以使用我的自定义TypeConverter
或ValueSerializer
实现。事实上,这种方法并没有奏效,但它让我尝试使用一种新的自定义类型来存储二进制字符串数据。
由于 CLRstring
类型是密封的,因此无法对其进行子类化,但可以通过创建自定义类型来实现类似的结果,该类型封装了字符串值以及将其与 Base64 进行转换所需的逻辑,以及隐式转换到/从string
它使其能够用作 CLRstring
类型的插件替代品。这是我对这种自定义类型的实现:
/// <summary>
/// Implements a string type that supports XAML serialization of non-printable characters via an associated type converter that converts to Base64 format.
/// </summary>
[TypeConverter(typeof(BinaryStringConverter))]
public class BinaryString
{
/// <summary>
/// Initializes a new instance of the <see cref="BinaryString"/> class and populates it with the passed string value.
/// </summary>
/// <param name="value">A <see cref="string"/> that represents the value with which to populate this instance.</param>
public BinaryString(string value)
{
Value = value;
}
/// <summary>
/// Gets the raw value of this instance.
/// </summary>
public string Value { get; private set; }
/// <summary>
/// Implements an implicit conversion from <see cref="string"/>.
/// </summary>
/// <param name="value">A <see cref="string"/> that represents the value to convert.</param>
/// <returns>A new <see cref="BinaryString"/> that represents the converted value.</returns>
public static implicit operator BinaryString(string value)
{
return new BinaryString(value);
}
/// <summary>
/// Implements an implicit conversion to <see cref="string"/>.
/// </summary>
/// <param name="value">A <see cref="BinaryString"/> that represents the value to convert.</param>
/// <returns>The <see cref="string"/> content of the passed value.</returns>
public static implicit operator string(BinaryString value)
{
return value.Value;
}
/// <summary>
/// Returns the value of this instance in <c>Base64</c> format.
/// </summary>
/// <returns>A <see cref="string"/> that represents the <c>Base64</c> value of this instance.</returns>
public string ToBase64String()
{
return Value == null ? null : Convert.ToBase64String(Encoding.UTF8.GetBytes(Value));
}
/// <summary>
/// Creates a new instance from the passed <c>Base64</c> string.
/// </summary>
/// <param name="value">A <see cref="string"/> that represent a <c>Base64</c> value to convert from.</param>
/// <returns>A new <see cref="BinaryString"/> instance.</returns>
public static BinaryString CreateFromBase64String(string value)
{
return new BinaryString(value == null ? null : Encoding.UTF8.GetString(Convert.FromBase64String(value)));
}
}
在这个角色中使用自定义类型的一个优点是关联的类型转换器可以直接在类级别应用,而不是在消费代码中修饰单个属性。这是类型转换器:
/// <summary>
/// Implements a mechanism to convert a <see cref="BinaryString"/> object to or from another object type.
/// </summary>
public class BinaryStringConverter : TypeConverter
{
/// <summary>
/// Returns whether this converter can convert the object to the specified type, using the specified context.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="destinationType">A <see cref="Type"/> that represents the type you want to convert to.</param>
/// <returns><c>true</c> if this converter can perform the conversion; otherwise, <c>false</c>.</returns>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
}
/// <summary>
/// Returns whether this converter can convert an object of the given type to the type of this converter, using the specified context.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="sourceType">A <see cref="Type"/> that represents the type you want to convert from.</param>
/// <returns><c>true</c> if this converter can perform the conversion; otherwise, <c>false</c>.</returns>
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
}
/// <summary>
/// Converts the given value object to the specified type, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="culture">A <see cref="CultureInfo"/>. If null is passed, the current culture is assumed.</param>
/// <param name="value">The <see cref="object"/> to convert.</param>
/// <param name="destinationType">A <see cref="Type"/> that represents the type you want to convert to.</param>
/// <returns>An <see cref="object"/> that represents the converted value.</returns>
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
return ((BinaryString)value).ToBase64String();
}
return base.ConvertTo(context, culture, value, destinationType);
}
/// <summary>
/// Converts the given object to the type of this converter, using the specified context and culture information.
/// </summary>
/// <param name="context">An <see cref="ITypeDescriptorContext"/> that provides a format context.</param>
/// <param name="culture">A <see cref="CultureInfo"/>. If null is passed, the current culture is assumed.</param>
/// <param name="value">The <see cref="object"/> to convert.</param>
/// <returns>An <see cref="object"/> that represents the converted value.</returns>
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
return BinaryString.CreateFromBase64String((string)value);
}
return base.ConvertFrom(context, culture, value);
}
}
有了这两个元素,消费代码几乎和以前一样 - 只是简单一点,因为它不再需要任何显式类型转换或值序列化属性......
public class MyClass
{
public BinaryString Name { get; set; }
}
...并且序列化过程与以前一样,只是它现在正确支持分配属性值中的不可打印字符:
var data = new MyClass
{
Name = "My\0Object"
};
var xaml = XamlServices.Save(data);
var deserialized = XamlServices.Parse(xaml) as MyClass;
请注意,我在此示例中已恢复使用XamlServices.Save()
and XamlServices.Parse()
,但此技术同样适用于XamlWriter.Save()
and XamlReader.Parse()
。