|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Xaml;
using System.Xaml.Replacements;
using MS.Internal.Serialization;
#pragma warning disable 1634, 1691 // suppressing PreSharp warnings
namespace System.Windows.Markup
{
/// <summary>
/// ValueSerializer allows a type to declare a serializer to control how the type is serialized to and from strings.
/// If a TypeConverter is declared for a type that converts to and from a string, a default value serializer will
/// be created for the type. The string values must be loss-less (i.e. converting to and from a string doesn't loose
/// data) and must be stable (i.e. returns the same string for the same value). If a type converter doesn't meet
/// these requirements, a custom ValueSerializer must be declared that meet the requirements or associate a null
/// ValueSerializer with the type to indicate the type converter should be ignored. Implementation of ValueSerializer
/// should avoid throwing exceptions. Any exceptions thrown could possibly terminate serialization.
/// </summary>
[TypeForwardedFrom("WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
public abstract class ValueSerializer
{
private static readonly object s_valueSerializersLock = new object();
private static Hashtable s_valueSerializers = new Hashtable();
/// <summary>
/// Constructor for a ValueSerializer
/// </summary>
protected ValueSerializer() { }
/// <summary>
/// Returns true if the given value can be converted to a string.
/// </summary>
/// <param name="value">Value to convert</param>
/// <param name="context">Context information</param>
/// <returns>Whether or not the value can be converted to a string</returns>
public virtual bool CanConvertToString(object? value, IValueSerializerContext? context)
{
return false;
}
/// <summary>
/// Returns true if the given value can be converted from a string.
/// </summary>
/// <param name="value">The string to convert</param>
/// <param name="context">Context information</param>
/// <returns>Whether or not the value can be converted from a string</returns>
public virtual bool CanConvertFromString(string? value, IValueSerializerContext? context)
{
return false;
}
/// <summary>
/// Converts the given value to a string for use in serialization. This method should only be
/// called if CanConvertToString returns true for the given value.
/// </summary>
/// <param name="value">The value to convert to a string</param>
/// <param name="context">Context information</param>
/// <returns>A string representation of value</returns>
public virtual string? ConvertToString(object? value, IValueSerializerContext? context)
{
throw GetConvertToException(value, typeof(string));
}
/// <summary>
/// Convert a string to an object. This method should only be called if CanConvertFromString
/// returns true for the given string.
/// </summary>
/// <param name="value">The string value to convert</param>
/// <param name="context">Context information</param>
/// <returns>An object corresponding to the string value</returns>
public virtual object? ConvertFromString(string value, IValueSerializerContext? context)
{
throw GetConvertFromException(value);
}
/// <summary>
/// Returns an enumeration of the types referenced by the value serializer. If the value serializer asks for
/// a value serializer for System.Type, any types it asks to convert should be supplied in the returned
/// enumeration. This allows a serializer to ensure a de-serializer has enough information about the types
/// this serializer converts.
///
/// Since a value serializer doesn't exist by default, it is important the value serializer be requested from
/// the IValueSerializerContext, not ValueSerializer.GetSerializerFor. This allows a serializer to encode
/// context information (such as xmlns definitions) to the System.Type converter (for example, which prefix
/// to generate).
/// </summary>
/// <param name="value">The value being serialized</param>
/// <param name="context">Context information</param>
/// <returns>An enumeration of the types converted by this serializer</returns>
public virtual IEnumerable<Type> TypeReferences(object? value, IValueSerializerContext? context)
{
return Array.Empty<Type>();
}
/// <summary>
/// Get the value serializer declared for the given type.
/// </summary>
/// <param name="type">The value type to serialize</param>
/// <returns>The value serializer associated with the given type</returns>
public static ValueSerializer? GetSerializerFor(Type type)
{
ArgumentNullException.ThrowIfNull(type);
object? value = s_valueSerializers[type];
if (value != null)
{
// This uses s_valueSerializersLock's instance as a sentinal for null (as opposed to not attempted yet).
return value == s_valueSerializersLock ? null : value as ValueSerializer;
}
AttributeCollection attributes = TypeDescriptor.GetAttributes(type);
ValueSerializer? result = null;
if (attributes[typeof(ValueSerializerAttribute)] is ValueSerializerAttribute attribute)
{
result = (ValueSerializer?)Activator.CreateInstance(attribute.ValueSerializerType);
}
if (result == null)
{
if (type == typeof(string))
{
result = new StringValueSerializer();
}
else
{
// Try to use the type converter
TypeConverter converter = TypeConverterHelper.GetTypeConverter(type);
// DateTime is a special-case. We can't use the DateTimeConverter, because it doesn't
// support anything other than user culture and invariant culture, and we need to specify
// en-us culture.
if (converter.GetType() == typeof(DateTimeConverter2))
{
result = new DateTimeValueSerializer();
}
else if (converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)) &&
!(converter is ReferenceConverter))
{
result = new TypeConverterValueSerializer(converter);
}
}
}
lock (s_valueSerializersLock)
{
// This uses s_valueSerializersLock's instance as a sentinal for null (as opposed to not attempted yet).
s_valueSerializers[type] = result ?? s_valueSerializersLock;
}
return result;
}
/// <summary>
/// Get the value serializer declared for the given property. ValueSerializer can be overriden by an attribute
/// on the property declaration.
/// </summary>
/// <param name="descriptor">PropertyDescriptor for the property to be serialized</param>
/// <returns>A value serializer associated with the given property</returns>
public static ValueSerializer? GetSerializerFor(PropertyDescriptor descriptor)
{
ArgumentNullException.ThrowIfNull(descriptor);
if (descriptor.Attributes[typeof(ValueSerializerAttribute)] is ValueSerializerAttribute serializerAttribute)
{
return (ValueSerializer?)Activator.CreateInstance(serializerAttribute.ValueSerializerType);
}
ValueSerializer? result = GetSerializerFor(descriptor.PropertyType);
if (result is null or TypeConverterValueSerializer)
{
TypeConverter converter = descriptor.Converter;
if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)) &&
converter is not ReferenceConverter)
{
result = new TypeConverterValueSerializer(converter);
}
}
return result;
}
/// <summary>
/// Get the value serializer declared for the given type. This version should be called whenever the caller
/// has a IValueSerializerContext to ensure that the correct value serializer is returned for the given
/// context.
/// </summary>
/// <param name="type">The value type to serialize</param>
/// <param name="context">Context information</param>
/// <returns>The value serializer associated with the given type</returns>
public static ValueSerializer? GetSerializerFor(Type type, IValueSerializerContext? context)
{
if (context != null)
{
ValueSerializer result = context.GetValueSerializerFor(type);
if (result != null)
{
return result;
}
}
return GetSerializerFor(type);
}
/// <summary>
/// Get the value serializer declared for the given property. ValueSerializer can be overriden by an attribute
/// on the property declaration. This version should be called whenever the caller has a
/// IValueSerializerContext to ensure that the correct value serializer is returned for the given context.
/// </summary>
/// <param name="descriptor">PropertyDescriptor for the property to be serialized</param>
/// <param name="context">Context information</param>
/// <returns>A value serializer associated with the given property</returns>
public static ValueSerializer? GetSerializerFor(PropertyDescriptor descriptor, IValueSerializerContext? context)
{
if (context != null)
{
ValueSerializer result = context.GetValueSerializerFor(descriptor);
if (result != null)
{
return result;
}
}
return GetSerializerFor(descriptor);
}
/// <summary>
/// Return a exception to throw if the value cannot be converted
/// </summary>
protected Exception GetConvertToException(object? value, Type destinationType)
{
ArgumentNullException.ThrowIfNull(destinationType);
string? text;
if (value == null)
{
text = SR.ToStringNull;
}
else
{
text = value.GetType().FullName;
}
return new NotSupportedException(SR.Format(SR.ConvertToException, base.GetType().Name, text, destinationType.FullName));
}
/// <summary>
/// Return a exception to throw if the string cannot be converted
/// </summary>
protected Exception GetConvertFromException(object? value)
{
string? text;
if (value == null)
{
text = SR.ToStringNull;
}
else
{
text = value.GetType().FullName;
}
return new NotSupportedException(SR.Format(SR.ConvertFromException, base.GetType().Name, text));
}
private static void TypeDescriptorRefreshed(RefreshEventArgs args)
{
s_valueSerializers = new Hashtable();
}
static ValueSerializer()
{
TypeDescriptor.Refreshed += TypeDescriptorRefreshed;
}
}
}
|