File: System\Windows\Forms\NativeToWinFormsAdapterTests.cs
Web Access
Project: src\src\System.Windows.Forms\tests\UnitTests\System.Windows.Forms.Tests.csproj (System.Windows.Forms.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System.Drawing;
using System.Reflection.Metadata;
using System.Text.RegularExpressions;
using System.Windows.Forms.TestUtilities;
using Com = Windows.Win32.System.Com;
 
namespace System.Windows.Forms.Tests;
 
public unsafe partial class NativeToWinFormsAdapterTests
{
    [GeneratedRegex(@"{[0-9]}")]
    private static partial Regex PlaceholdersPattern();
 
    private static string InvalidTypeFormatCombinationMessage =>
        PlaceholdersPattern().Replace(SR.ClipboardOrDragDrop_InvalidFormatTypeCombination, "*");
    private static string TypeRequiresResolverMessage => PlaceholdersPattern().Replace(SR.ClipboardOrDragDrop_InvalidType, "*");
    private static string UseTryGetDataWithResolver => PlaceholdersPattern().Replace(SR.ClipboardOrDragDrop_UseTypedAPI, "*");
 
    private const string FormatterDisabledMessage =
        "BinaryFormatter serialization and deserialization are disabled within this application. See https://aka.ms/binaryformatter for more information.";
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsObject_Primitive_Success(string format)
    {
        DataObject native = new();
        // Primitive type is serialized by generating the record field by field.
        native.SetData(format, 1);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        // Format is compatible with `object` type, validation passed.
        // The `int` type can be assigned to an `object`, thus record type matched the requested type.
        // The primitive type is read from the serialization record field by field.
        dataObject.TryGetData(format, out object? value).Should().BeTrue();
        value.Should().Be(1);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsObject_Primitive_RequiresResolver(string format)
    {
        DataObject native = new();
        // Primitive type is serialized by generating the record field by field.
        native.SetData(format, 1);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        Action tryGetData = () => dataObject.TryGetData(format, out object? value);
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: TypeRequiresResolverMessage);
 
        dataObject.TryGetData(format, Resolver, autoConvert: false, out object? value).Should().BeTrue();
        value.Should().Be(1);
 
        static Type Resolver(TypeName typeName) => typeof(int);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_AsObject_Primitive_InvalidTypeFormatCombination(string format)
    {
        DataObject native = new();
        native.SetData(format, 1);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        // Throw when validating arguments, as these formats allow exactly strings or bitmaps only.
        Action tryGetData = () => dataObject.TryGetData(format, out object? _);
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    private static (DataObject dataObject, TestData value) SetDataObject(string format)
    {
        DataObject native = new();
        TestData value = new(new(6, 7));
        // This code does not flush the data.
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        return (dataObject, value);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsObject_Custom_RequiresResolver(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, out object? _);
 
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: TypeRequiresResolverMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsObject_Custom_FormatterEnabled_RequiresResolver(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, out object? _);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: TypeRequiresResolverMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_AsObject_Custom_InvalidTypeFormatCombination(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, out object? _);
 
        // Type-Format combination is validated before the we attempt to serialize data.
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsObject_Custom_ReturnsNotSupportedException(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        // SetData writes NotSupportedException to HGLOBAL to indicate that formatters are disabled,
        // but the restricted format can't read it.
        dataObject.TryGetData(format, out object? result).Should().BeFalse();
        result.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsObject_Custom_FormatterEnabled_ReturnsFalse(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        dataObject.TryGetData(format, out object? result).Should().BeFalse();
        result.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsInterface_ListOfPrimitives_Success(string format)
    {
        DataObject native = new();
        List<int> value = [1];
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        dataObject.TryGetData(format, out IList<int>? list).Should().BeTrue();
        list.Should().BeEquivalentTo(value);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsInterface_ListOfPrimitives_RequiresResolver(string format)
    {
        DataObject native = new();
        List<int> value = [1];
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        // Theoretically we don't require a resolver here, but this is an exception. In the more common cases resolver
        // is required to instantiate non-concrete types.
        Action tryGetData = () => dataObject.TryGetData(format, out IList<int>? _);
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: TypeRequiresResolverMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_AsInterface_ListOfPrimitives_InvalidTypeFormatCombination(string format)
    {
        DataObject native = new();
        List<int> value = [1];
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        Action tryGetData = () => dataObject.TryGetData(format, out IList<int>? _);
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsConcreteType_ListOfPrimitives_Success(string format)
    {
        DataObject native = new();
        List<int> value = [1];
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        dataObject.TryGetData(format, out List<int>? list).Should().BeTrue();
        list.Should().BeEquivalentTo(value);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_AsConcreteType_ListOfPrimitives_InvalidTypeFormatCombination(string format)
    {
        DataObject native = new();
        List<int> value = [1];
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        Action tryGetData = () => dataObject.TryGetData(format, out List<int>? _);
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsConcreteType_Custom_FormatterEnabled_RequiresResolver(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, out TestData? _);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: UseTryGetDataWithResolver);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsConcreteType_Custom_FormatterEnabled_ReturnsFalse(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        // Format-type combination is invalid, serialization threw an exception.
        dataObject.TryGetData(format, out TestData? testData).Should().BeFalse();
        testData.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsConcreteType_Custom_FormattersDisabled_ReturnFalse(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        // Formatters are not supported, HGLOBAL contains NotSupportedException
        // but these formats are not compatible with this type on the clipboard.
        dataObject.TryGetData(format, out TestData? testData).Should().BeFalse();
        testData.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_AsConcreteType_Custom_InvalidTypeFormatCombination(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        Action tryGetData = () => dataObject.TryGetData(format, out TestData? _);
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_WithResolver_AsConcreteType_Custom_FormatterEnabled_Success(string format)
    {
        (DataObject dataObject, TestData value) = SetDataObject(format);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out TestData? testData).Should().BeTrue();
        testData.Should().BeEquivalentTo(value);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_WithResolver_AsConcreteType_Custom_InvalidTypeFormatCombination(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out TestData? _);
 
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_WithResolver_AsConcreteType_Custom_FormatterEnabled_InvalidTypeFormatCombination(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out TestData? _);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_WithResolver_AsConcreteType_Custom_FormatterDisabledException(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        // Formatter is not enabled, HGLOBAL contains NotSupportedException, we can't read it, assume wrong type on the clipboard.
        dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out TestData? testData).Should().BeFalse();
        testData.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_AsAbstract_Custom_FormatterEnabled_ReturnFalse(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        // Format-type combination is invalid, serialization threw an exception.
        dataObject.TryGetData(format, out AbstractBase? testData).Should().BeFalse();
        testData.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_AsAbstract_Custom_InvalidTypeFormatCombination(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        Action tryGetData = () => dataObject.TryGetData(format, out AbstractBase? _);
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsAbstract_Custom_RequiresResolver(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, out AbstractBase? _);
 
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: TypeRequiresResolverMessage);
 
        dataObject.TryGetData(format, out NotSupportedException? ex).Should().BeTrue();
        ex.Should().BeOfType<NotSupportedException>().Which.Message.Should().Be(FormatterDisabledMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsAbstract_Custom_FormatterEnabled_RequiresResolver(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
        Action tryGetData = () => dataObject.TryGetData(format, out AbstractBase? _);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        tryGetData.Should().Throw<NotSupportedException>().WithMessage(expectedWildcardPattern: TypeRequiresResolverMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UndefinedRestrictedFormat))]
    public void TryGetData_WithResolver_AsAbstract_Custom_FormatterEnabled_ReturnFalse(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        // Format-type combination is validated, but serialization threw an exception
        dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out AbstractBase? testData).Should().BeFalse();
        testData.Should().BeNull();
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_WithResolver_AsAbstract_Custom_FormatterEnabled_Success(string format)
    {
        (DataObject dataObject, TestData value) = SetDataObject(format);
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out AbstractBase? testData).Should().BeTrue();
        testData.Should().BeEquivalentTo(value);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void TryGetData_WithResolver_AsAbstract_Custom_InvalidTypeFormatCombination(string format)
    {
        (DataObject dataObject, TestData _) = SetDataObject(format);
 
        // Nothing is written to HGLOBAL in this test because format-type combination is invalid.
        Action tryGetData = () => dataObject.TryGetData(format, TestData.Resolver, autoConvert: true, out AbstractBase? _);
        tryGetData.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.UnboundedFormat))]
    public void TryGetData_AsConcrete_NotSerializable_FormatterEnabled_ReturnFalse(string format)
    {
        DataObject native = new();
        NotSerializableData value = new(1);
        native.SetData(format, value);
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
 
        using BinaryFormatterScope scope = new(enable: true);
        using BinaryFormatterInClipboardDragDropScope clipboardScope = new(enable: true);
 
        // E_UNEXPECTED and a NULL HGLOBAL is returned from the COM GetData, we have no stream to deserialize.
        dataObject.TryGetData(format, out NotSerializableData? data).Should().BeFalse();
        data.Should().BeNull();
    }
 
    [WinFormsFact]
    public void SetDataAsJson_TryGetData_Requires_Resolver()
    {
        SimpleTestData value = new("text", new(10, 10));
 
        DataObject native = new();
        native.SetDataAsJson("test", value);
 
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
        Action a = () => dataObject.TryGetData("test", out SimpleTestDataBase? _);
        a.Should().Throw<NotSupportedException>();
        // This requires a resolver because this simulates out of process scenario with a type that is not intrinsic and not supported.
        dataObject.TryGetData("test", SimpleTestData.Resolver, autoConvert: false, out SimpleTestDataBase? deserialized).Should().BeTrue();
        var deserializedChecked = deserialized.Should().BeOfType<SimpleTestDataBase>().Subject;
        deserializedChecked.Text.Should().Be(value.Text);
    }
 
    [WinFormsTheory]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.StringFormat))]
    [CommonMemberData(typeof(DataObjectTestHelpers), nameof(DataObjectTestHelpers.BitmapFormat))]
    public void SetDataAsJson_InvalidTypeFormatCombination(string format)
    {
        SimpleTestData value = new("text", new(10, 10));
 
        DataObject native = new();
        native.SetDataAsJson(format, value);
 
        DataObject dataObject = new(ComHelpers.GetComPointer<Com.IDataObject>(native));
        Action a = () => dataObject.TryGetData(format, out SimpleTestDataBase? _);
 
        a.Should().Throw<NotSupportedException>()
            .WithMessage(expectedWildcardPattern: InvalidTypeFormatCombinationMessage);
    }
 
    private class SimpleTestDataBase
    {
        public string? Text { get; set; }
    }
 
    private class SimpleTestData : SimpleTestDataBase
    {
        public SimpleTestData(string text, Point point)
        {
            Text = text;
            Point = point;
        }
 
        public Point Point { get; set; }
 
        public static Type Resolver(TypeName typeName)
        {
            (string name, Type type)[] allowedTypes =
            [
                (typeof(SimpleTestData).FullName!, typeof(SimpleTestData)),
                (typeof(SimpleTestDataBase).FullName!, typeof(SimpleTestDataBase)),
            ];
 
            string fullName = typeName.FullName;
            foreach (var (name, type) in allowedTypes)
            {
                // Namespace-qualified type name.
                if (name == fullName)
                {
                    return type;
                }
            }
 
            throw new NotSupportedException($"Can't resolve {fullName}");
        }
    }
 
    // This class does not have [Serializable] attribute, serialization stream will be corrupt.
    private class NotSerializableData
    {
        public NotSerializableData(int value)
        {
            Value = value;
        }
 
        public int Value;
    }
 
    [Serializable]
    private class TestData : AbstractBase
    {
        public TestData(Point point)
        {
            Location = point;
            Inner = new InnerData("inner");
        }
 
        public Point Location;
        public InnerData? Inner;
 
        public override void DoStuff() { }
 
        [Serializable]
        internal class InnerData
        {
            public InnerData(string text)
            {
                Text = text;
            }
 
            public string Text;
        }
 
        public static Type Resolver(TypeName typeName)
        {
            (string name, Type type)[] allowedTypes =
            [
                (typeof(TestData).FullName!, typeof(TestData)),
                (typeof(InnerData).FullName!, typeof(InnerData)),
                (typeof(AbstractBase).FullName!, typeof(AbstractBase)),
            ];
 
            string fullName = typeName.FullName;
            foreach (var (name, type) in allowedTypes)
            {
                // Namespace-qualified type name.
                if (name == fullName)
                {
                    return type;
                }
            }
 
            throw new NotSupportedException($"Can't resolve {fullName}");
        }
    }
 
    [Serializable]
    internal abstract class AbstractBase
    {
        public abstract void DoStuff();
    }
}