Подтвердить что ты не робот

Буфер обмена ведет себя по-разному в .NET 3.5 и 4, но почему?

Недавно мы обновили очень большой проект из .NET framework 3.5 до 4, и изначально все, казалось, работало одинаково. Но теперь на операции копирования пасты начали появляться ошибки. Мне удалось создать небольшое воспроизводимое приложение, которое показывает различное поведение в .NET 3.5 и 4. Я также нашел обходное решение (вручную сериализует данные в буфер обмена), но мне остается знать, "почему" есть разница в поведении.

Это небольшое тестовое приложение, которое я сделал:

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Windows.Forms;

namespace ClipboardTest
{
    public class Program
    {
        [Serializable]
        public class Element
        {
            public Element(string name)
            {
                this.name = name;
            }

            public string name;
        }

        public static List<Element> TestSerializer(List<Element> obj)
        {
            var memoryStream = new MemoryStream();
            var formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, obj);
            return (List<Element>)formatter.Deserialize(new MemoryStream(memoryStream.GetBuffer()));
        }

        public static List<Element> TestClipboard(List<Element> obj)
        {
            Clipboard.SetDataObject(obj);
            return (List<Element>)Clipboard.GetDataObject().GetData(typeof(List<Element>));
        }

        public static void DumpObject(string testName, List<Element> obj)
        {
            if (obj == null)
            {
                Console.WriteLine("{0} : List is null", testName);
                return;
            }
            foreach (var prop in obj)
            {
                Console.WriteLine("{0} : {1}", testName, prop.name);
            }
        }

        [STAThread]
        static void Main()
        {
            var copyData = new List<Element> { new Element("all good") };
            DumpObject("Serializer", TestSerializer(copyData));
            DumpObject("Clipboard", TestClipboard(copyData));
        }
    }
}

Выход .NET 3.5:
 Сериализатор: все хорошо
 Буфер обмена: все хорошо

Выход .NET 4:
Сериализатор: все хорошо
Буфер обмена: Список имеет значение null

Я просмотрел источник .NET для класса Clipboard и DataObject, но я не мог понять, какой сериализатор использовался. В документации MSDN говорится, что тип должен быть сериализуемым, что в данном случае относится к классам List < > и Element. Копирование объекта Element работает очень хорошо, но как только я копирую список элементов, он ломается.

Чтобы протестировать, я создал 2 проекта "Консольное приложение" на Visual Studio 2010 с пакетом обновления 1 (SP1). Первый проект, который я оставил с настройкой "Target framework" по умолчанию ".NET Framework 4 Client Profile". Второй проект, который я изменил для использования ".NET Client 3.5 Client Profile".

Дополнительная информация о моей версии DLL для форм:
Исходное имя файла: System.Windows.Forms.dll
Версия файла/версия Project: 4.0.30319.235
Язык: английский (США)
Дата изменения: 16-02-2012 22:50

4b9b3361

Ответ 1

Я повторяю. Вы можете получить больше информации об ошибке с помощью Debug + Exceptions, отметьте флажок "Бросок" для исключений CLR. Это остановит программу, когда внутреннее исключение будет выбрано кодом буфера обмена в рамках. Метод реализации IDataObject.GetDataHere() завершился с ошибкой COM, "Недопустимая структура FORMATETC (Исключение из HRESULT: 0x80040064 (DV_E_FORMATETC))".

В формате есть что-то не так. Это становится ясным, когда вы устанавливаете точку останова после оператора Clipboard.SetDataObject(obj). И поставьте Clipboard.GetDataObject(). GetFormats() в выражении для просмотра отладчика. Я вижу:

"System.Collections.Generic.List`1 [[ClipboardTest.Program + Element, ConsoleApplication1, Version = 1.0.0.0, Culture = neutral, Public"

Обратите внимание, что строка усечена, часть PublicKeyToken искалечена. Вы можете произвольно изменить эту усеченную строку, изменив имя пространства имен и название проекта. Сделайте их достаточно короткими, и программа не подведет.

Очевидно, что это и есть причина проблемы. Длина строки ограничена 127 символами, любой тип, полное имя которого больше, чем это приведет к этому сбою. С большой вероятностью, что это будет общий тип, поскольку у них очень длинные имена.

Сообщите об этой ошибке на сайте connect.microsoft.com. Ваш код демонстрирует ошибку очень хорошо, достаточно опубликовать ссылку на нее в вашем отчете об ошибке. У меня нет очень хорошего обходного пути, потому что имя достаточно короткое, не очень практично. Но вы можете с кодом вроде этого:

        // Put it on the clipboard, use a wrapper type with a short name
        var envelope = new List<object>();
        envelope.AddRange(obj);
        Clipboard.SetDataObject(envelope);

        // Retrieve from clipboard, unwrap back to original type
        envelope = (List<object>)Clipboard.GetDataObject().GetData(typeof(List<object>));
        var retval = new List<Element>();
        retval.AddRange(envelope.Cast<Element>());
        return retval;

ОБНОВЛЕНИЕ: эта ошибка сообщается исправленной в VS2013.