Compartir por


Cambios en el Portapapeles de Windows Forms y DataObject en .NET 10

En este artículo, se muestra cómo actualizar el Portapapeles de Windows Forms y las operaciones de arrastrar y soltar a las nuevas API de tipo seguro en .NET 10. Aprenderá a usar los nuevos Clipboard.TryGetData métodos y Clipboard.SetDataAsJson<T>(String, T) , comprender qué tipos integrados funcionan sin cambios y detectar estrategias para controlar tipos personalizados y datos heredados después de la eliminación de BinaryFormatter.

BinaryFormatter se quitó del entorno de ejecución en .NET 9 debido a vulnerabilidades de seguridad. Este cambio rompió el portapapeles y las operaciones de arrastrar y soltar con objetos personalizados. .NET 10 presenta nuevas API que usan métodos de serialización JSON y seguros para tipos para restaurar esta funcionalidad, mejorar la seguridad y proporcionar un mejor control de errores y compatibilidad entre procesos.

Clipboard.SetData(String, Object) ya no funciona con tipos personalizados que requieren BinaryFormatter para la serialización. Este método realiza una operación de copia (de forma predeterminada) que serializa los datos inmediatamente, pero esa serialización ahora produce un error en los tipos que necesitan BinaryFormatter porque se ha quitado. Clipboard.GetData(String) está obsoleto en .NET 10. Cuando BinaryFormatter es necesario para la deserialización, pero no está habilitado, GetData() devuelve una NotSupportedException instancia. Use los nuevos Clipboard.TryGetData métodos y Clipboard.SetDataAsJson<T>(String, T) para las operaciones seguras para tipos y la serialización JSON de objetos personalizados.

En las secciones siguientes se proporcionan instrucciones detalladas sobre la migración, se explican los tipos que funcionan sin cambios y se muestra cómo controlar escenarios de datos nuevos y heredados.

Prerrequisitos

Antes de continuar, revise estos conceptos:

  • Cómo usaban las aplicaciones BinaryFormatter en escenarios de Portapapeles y arrastrar y colocar antes de .NET 9.
  • Vulnerabilidades de seguridad que llevaron a la eliminación de BinaryFormatter.
  • Cómo trabajar con System.Text.Json patrones de serialización y sus limitaciones.

Para obtener más información, consulte estos artículos:

Cambios importantes de la eliminación de BinaryFormatter

Al quitar BinaryFormatter en .NET 9 fundamentalmente se cambia la forma en que Windows Forms controla el Portapapeles y las operaciones de arrastrar y colocar con tipos personalizados. Estos cambios afectan a los patrones de código existentes y requieren una migración cuidadosa para mantener la funcionalidad.

Los tipos personalizados ya no se serializan automáticamente

En .NET 8 y versiones anteriores, podría colocar cualquier objeto personalizado serializable en el Portapapeles llamando a SetData(). El BinaryFormatter manejó la serialización automáticamente. A partir de .NET 9, SetData() sigue realizando una operación de copia que serializa los datos inmediatamente, pero se produce un error en esta serialización para los tipos que requieren BinaryFormatter porque se ha quitado.

El código siguiente ya no funciona:

[Serializable]
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public static void BrokenCustomTypeExample()
{
    // This worked in .NET 8 and earlier but silently fails starting with .NET 9
    Person person = new Person { Name = "John", Age = 30 };
    Clipboard.SetData("MyApp.Person", person);  // No data is stored

    // Later attempts to retrieve the data return a NotSupportedException instance
    object data = Clipboard.GetData("MyApp.Person");
}
<Serializable>
Public Class Person
    Public Property Name As String
    Public Property Age As Integer
End Class

Public Shared Sub BrokenCustomTypeExample()
    ' This worked in .NET 8 and earlier but silently fails starting with .NET 9
    Dim person As New Person With {.Name = "John", .Age = 30}
    Clipboard.SetData("MyApp.Person", person)  ' No data is stored

    ' Later attempts to retrieve the data return a NotSupportedException instance
    Dim data As Object = Clipboard.GetData("MyApp.Person")
End Sub

Lo que podría ver

  • El SetData() método se completa sin lanzar una excepción.
  • Los datos se colocan en el Portapapeles, pero se produce un error de serialización para los tipos que requieren BinaryFormatter.
  • Intentos posteriores de recuperar los datos con GetData() devuelven una instancia NotSupportedException que indica que BinaryFormatter es necesaria pero no está habilitada.

Guía de migración

Use el nuevo SetDataAsJson<T>() método o serialice manualmente en un string o un byte[]. Para obtener más información, consulte la sección Trabajar con tipos personalizados .

GetData() está obsoleto: use TryGetData<T>() en su lugar.

El método heredado GetData() está obsoleto en .NET 10. Este método devuelve datos correctamente en la mayoría de los casos, pero cuando BinaryFormatter es necesario para la deserialización y no está habilitado, GetData() devuelve una NotSupportedException instancia que indica BinaryFormatter que es necesaria. Debe migrar a los nuevos métodos de tipos seguros TryGetData<T>() para un mejor control de errores y seguridad de tipos.

En el ejemplo siguiente se muestra el patrón obsoleto que debe evitar:

public static void ObsoleteGetDataExample()
{
    // Don't use - GetData() is obsolete in .NET 10
    object data = Clipboard.GetData("MyApp.Person");  // Obsolete method

    // Returns a NotSupportedException instance for a custom object type
    if (data is Person person)
    {
        Console.WriteLine($"Processing person: {person.Name}, Age: {person.Age}");
    }
}
Public Shared Sub ObsoleteGetDataExample()
    ' Don't use - GetData() is obsolete in .NET 10
    Dim data As Object = Clipboard.GetData("MyApp.Person")  ' Obsolete method

    ' Returns a NotSupportedException instance for a custom object type
    If TypeOf data Is Person Then
        Dim person As Person = DirectCast(data, Person)
        Console.WriteLine($"Processing person: {person.Name}, Age: {person.Age}")
    End If
End Sub

En su lugar, utilice el enfoque moderno seguro de tipos con TryGetData<T>():

public static void ModernTryGetDataExample()
{
    var data = new Person { Name = "Alice", Age = 28 };
    Clipboard.SetDataAsJson("MyAppData", data);

    // Use this - type-safe approach with TryGetData<T>()
    if (Clipboard.TryGetData("MyApp.Person", out Person person))
    {
        // person is guaranteed to be the correct type
        Console.WriteLine($"Processing person: {person.Name}, Age: {person.Age}");
    }
    else
    {
        // Handle the case where data isn't available or is the wrong type
        MessageBox.Show("Unable to retrieve person data from clipboard");
    }
}
Public Shared Sub ModernTryGetDataExample()
    Dim data As New Person With {.Name = "Alice", .Age = 30}
    Clipboard.SetDataAsJson("MyAppData", data)

    ' Use this - type-safe approach with TryGetData(Of T)()
    Dim person As Person = Nothing
    If Clipboard.TryGetData("MyApp.Person", person) Then
        ' person is guaranteed to be the correct type
        Console.WriteLine($"Processing person: {person.Name}, Age: {person.Age}")
    Else
        ' Handle the case where data isn't available or is the wrong type
        MessageBox.Show("Unable to retrieve person data from clipboard")
    End If
End Sub

Ventajas de TryGetData<T>()

  • Seguridad de tipos: no es necesario realizar conversiones, el método devuelve el tipo exacto que solicita.
  • Manejo claro de errores: devuelve un indicador de éxito booleano en lugar de usar patrones nulo o de excepción.
  • A prueba de futuro: Diseñado para funcionar con nuevos métodos de serialización y soporte de datos heredados.

Identificación del código afectado

Busque:

  • Cualquier GetData() llamada, ya que todo el método está obsoleto independientemente del tipo de datos.
  • Uso de DataObject.GetData() y IDataObject.GetData() en las operaciones de arrastrar y colocar.

Guía de migración

Reemplace toda la utilización de GetData() por métodos seguros de tipo TryGetData<T>(). Para obtener ejemplos completos de todas las sobrecargas, consulte la sección Nuevas API seguras para tipos .

Nuevas API seguras para tipos

.NET 10 presenta tres nuevas familias de API que proporcionan seguridad de tipos, mejor control de errores y compatibilidad con la serialización JSON para portapapeles y operaciones de arrastrar y colocar:

Métodos TryGetData<T>()

La TryGetData<T>() familia reemplaza el método obsoleto GetData() . Ofrece una recuperación segura de tipos y una indicación clara de éxito o fracaso para las operaciones del portapapeles.

Recuperación básica segura de tipos

public static void BasicTypeSafeRetrievalExamples()
{
    // Retrieve text data using a standard format
    if (Clipboard.TryGetData(DataFormats.Text, out string textData))
        Console.WriteLine($"Text: {textData}");

    // Retrieve an integer using a custom format
    if (Clipboard.TryGetData("NumberData", out int numberData))
        Console.WriteLine($"Number: {numberData}");

    // Retrieve Unicode text using a standard format
    if (Clipboard.TryGetData(DataFormats.UnicodeText, out string unicodeText))
        Console.WriteLine($"Unicode: {unicodeText}");

    // Retrieve raw text data with OLE conversion control
    if (Clipboard.TryGetData(DataFormats.Text, out string rawText))
        Console.WriteLine($"Raw: {rawText}");

    // Retrieve file drops using a standard format
    if (Clipboard.TryGetData(DataFormats.FileDrop, out string[] files))
        Console.WriteLine($"Files: {string.Join(", ", files)}");
}
Public Shared Sub BasicTypeSafeRetrievalExamples()
    ' Retrieve text data using a standard format
    Dim textData As String = Nothing
    If Clipboard.TryGetData(DataFormats.Text, textData) Then
        Console.WriteLine($"Text: {textData}")
    End If

    ' Retrieve an integer using a custom format
    Dim numberData As Integer
    If Clipboard.TryGetData("NumberData", numberData) Then
        Console.WriteLine($"Number: {numberData}")
    End If

    ' Retrieve Unicode text using a standard format
    Dim unicodeText As String = Nothing
    If Clipboard.TryGetData(DataFormats.UnicodeText, unicodeText) Then
        Console.WriteLine($"Unicode: {unicodeText}")
    End If

    ' Retrieve raw text data with OLE conversion control
    Dim rawText As String = Nothing
    If Clipboard.TryGetData(DataFormats.Text, rawText) Then
        Console.WriteLine($"Raw: {rawText}")
    End If

    ' Retrieve file drops using a standard format
    Dim files As String() = Nothing
    If Clipboard.TryGetData(DataFormats.FileDrop, files) Then
        Console.WriteLine($"Files: {String.Join(", ", files)}")
    End If
End Sub

Tipos JSON personalizados

public static void CustomJsonTypesExamples()
{
    // Retrieve a custom type stored with SetDataAsJson<T>()
    if (Clipboard.TryGetData("Person", out Person person))
        Console.WriteLine($"Person: {person.Name}");

    // Retrieve application-specific data formats
    if (Clipboard.TryGetData("MyApp.Settings", out AppSettings settings))
        Console.WriteLine($"Settings: {settings.Theme}");

    // Retrieve complex custom objects
    if (Clipboard.TryGetData("DocumentData", out DocumentInfo doc))
        Console.WriteLine($"Document: {doc.Title}");
}
Public Shared Sub CustomJsonTypesExamples()
    ' Retrieve a custom type stored with SetDataAsJson(Of T)()
    Dim person As Person = Nothing
    If Clipboard.TryGetData("Person", person) Then
        Console.WriteLine($"Person: {person.Name}")
    End If

    ' Retrieve application-specific data formats
    Dim settings As AppSettings = Nothing
    If Clipboard.TryGetData("MyApp.Settings", settings) Then
        Console.WriteLine($"Settings: {settings.Theme}")
    End If

    ' Retrieve complex custom objects
    Dim doc As DocumentInfo = Nothing
    If Clipboard.TryGetData("DocumentData", doc) Then
        Console.WriteLine($"Document: {doc.Title}")
    End If
End Sub

Métodos setDataAsJson<T>()

Estos métodos proporcionan serialización JSON automática mediante System.Text.Json con almacenamiento seguro para tipos.

Especificar un formato personalizado

public static void CustomFormatExample()
{
    var settings = new AppSettings { Theme = "Dark", AutoSave = true };

    // Use a custom format for better organization
    Clipboard.SetDataAsJson("MyApp.Settings", settings);
}
Public Shared Sub CustomFormatExample()
    Dim settings As New AppSettings With {.Theme = "Dark", .AutoSave = True}

    ' Use a custom format for better organization
    Clipboard.SetDataAsJson("MyApp.Settings", settings)
End Sub

Inferencia de formato automático

Al especificar el nombre de tipo como formato de datos, TryGetData<T> puede deducir automáticamente el formato:

public static void AutomaticFormatInferenceExample()
{
    var person = new Person { Name = "Alice", Age = 25 };

    // Use the type name as the format
    Clipboard.SetDataAsJson(typeof(Person).FullName, person);

    // Retrieve the data and infer the format automatically
    if (Clipboard.TryGetData(out Person retrievedPerson))
    {
        Console.WriteLine($"Retrieved: {retrievedPerson.Name}");
    }
}
Public Shared Sub AutomaticFormatInferenceExample()
    Dim person As New Person With {.Name = "Alice", .Age = 25}

    ' Use the type name as the format
    Clipboard.SetDataAsJson(GetType(Person).FullName, person)

    ' Retrieve the data and infer the format automatically
    Dim retrievedPerson As Person = Nothing
    If Clipboard.TryGetData(retrievedPerson) Then
        Console.WriteLine($"Retrieved: {retrievedPerson.Name}")
    End If
End Sub

Interfaz ITypedDataObject

La interfaz ITypedDataObject habilita operaciones de arrastrar y soltar seguras para tipos al extender IDataObject con métodos tipados.

A partir de .NET 10, DataObject (un tipo común en escenarios de arrastrar y soltar) implementa ITypedDataObject.

Uso de ITypedDataObject en escenarios de arrastrar y colocar

private void OnDragDrop(object sender, DragEventArgs e)
{
    if (e.Data is ITypedDataObject typedData)
    {
        // Retrieve files from drag data using a standard format
        if (typedData.TryGetData(DataFormats.FileDrop, out string[] files))
            Console.WriteLine($"Dropped files: {string.Join(", ", files)}");

        // Retrieve text using a standard format
        if (typedData.TryGetData(DataFormats.Text, out string text))
            Console.WriteLine($"Dropped text: {text}");

        // Retrieve custom items using an application-specific format
        if (typedData.TryGetData("CustomItem", out MyItem item))
            Console.WriteLine($"Dropped custom item: {item.Name} = {item.Value}");
    }
}
Private Sub OnDragDrop(sender As Object, e As DragEventArgs)
    If TypeOf e.Data Is ITypedDataObject Then
        Dim typedData As ITypedDataObject = CType(e.Data, ITypedDataObject)

        ' Retrieve files from drag data using a standard format
        Dim files As String() = Nothing
        If typedData.TryGetData(DataFormats.FileDrop, files) Then
            Console.WriteLine($"Dropped files: {String.Join(", ", files)}")
        End If

        ' Retrieve text using a standard format
        Dim text As String = Nothing
        If typedData.TryGetData(DataFormats.Text, text) Then
            Console.WriteLine($"Dropped text: {text}")
        End If

        ' Retrieve custom items using an application-specific format
        Dim item As MyItem = Nothing
        If typedData.TryGetData("CustomItem", item) Then
            Console.WriteLine($"Dropped custom item: {item.Name} = {item.Value}")
        End If
    End If
End Sub

Tipos que no requieren serialización JSON

Muchos tipos de .NET integrados funcionan con operaciones del Portapapeles sin necesidad de serialización ni compatibilidad con JSON. Estos tipos se serializan automáticamente en el formato binario de comunicación remota de .NET (NRBF), que proporciona almacenamiento eficaz y mantiene la seguridad de tipos.

Estos tipos usan NRBF, el mismo formato binario eficaz usado por el heredado BinaryFormatter. La serialización NRBF proporciona estas ventajas clave:

  • Representación binaria compacta: permite el almacenamiento y la transferencia eficaces.
  • Información de tipo integrada: conserva los tipos exactos de .NET durante las operaciones de ida y vuelta.
  • Compatibilidad entre procesos: funciona entre diferentes aplicaciones de .NET.
  • Serialización automática: tipos serializan sin código personalizado.

Nota:

Métodos como SetData() realizan una operación de copia que procede a serializar los datos inmediatamente, lo que garantiza que los datos del Portapapeles se conserven incluso después de finalizar el proceso actual. Si usa el DataObject tipo directamente con SetDataObject(dataObject, copy), puede controlar cuándo se produce la serialización. Al establecer el copy parámetro en true (valor predeterminado), se fuerza la serialización inmediata, mientras que false se aplaza la serialización hasta que sea necesario. Con copy configurado a false y los datos se recuperan durante el mismo proceso, es posible que la serialización no sea necesaria, pero los datos no persistirán después de que el proceso termine.

Para obtener detalles técnicos, consulte la especificación formato binario de comunicación remota de .NET.

Las clases que admiten datos codificados con NRBF se implementan en el System.Formats.Nrbf espacio de nombres.

Garantías de seguridad de tipos

Windows Forms proporciona varios mecanismos de seguridad para estos tipos integrados:

  • Coincidencia exacta de tipos. TryGetData devuelve solo el tipo solicitado.
  • Validación automática. Windows Forms valida la compatibilidad de tipos durante la deserialización.
  • No hay ejecución arbitraria de código. A diferencia de los tipos personalizados con BinaryFormatter, estos tipos no pueden ejecutar código malintencionado.
  • Se requiere validación de contenido. Aún debe validar el contenido y los intervalos de datos para la lógica de la aplicación.
  • Sin restricciones de tamaño. Las matrices grandes o mapas de bits no se limitan automáticamente. Supervisar el uso de memoria.

Tipos primitivos admitidos

Los siguientes tipos primitivos funcionan sin problemas con el Portapapeles y las operaciones DataObject. No necesita configuraciones ni serialización personalizadas. El sistema del Portapapeles maneja automáticamente estos tipos integrados de .NET:

  • bool, byte, char, decimal, double, short, int y long.
  • sbyte, ushort, uint, ulong, float, y string.
  • TimeSpan y DateTime.

En los siguientes ejemplos se muestra cómo estos tipos primitivos funcionan directamente con los métodos SetData() y TryGetData<T>().

public static void PrimitiveTypesExample()
{
    // Numeric types
    Clipboard.SetData("MyInt", 42);
    Clipboard.SetData("MyDouble", 3.14159);
    Clipboard.SetData("MyDecimal", 123.45m);

    // Text and character types
    Clipboard.SetData("MyString", "Hello World");
    Clipboard.SetData("MyChar", 'A');

    // Boolean and date/time types
    Clipboard.SetData("MyBool", true);
    Clipboard.SetData("MyDateTime", DateTime.Now);
    Clipboard.SetData("MyTimeSpan", TimeSpan.FromMinutes(30));

    // Later retrieval with type safety
    if (Clipboard.TryGetData("MyTimeSpan", out TimeSpan value))
    {
        Console.WriteLine($"Clipboard value is: {value}");
    }
}
Public Shared Sub PrimitiveTypesExample()
    ' Numeric types
    Clipboard.SetData("MyInt", 42)
    Clipboard.SetData("MyDouble", 3.14159)
    Clipboard.SetData("MyDecimal", 123.45D)

    ' Text and character types
    Clipboard.SetData("MyString", "Hello World")
    Clipboard.SetData("MyChar", "A"c)

    ' Boolean and date/time types
    Clipboard.SetData("MyBool", True)
    Clipboard.SetData("MyDateTime", DateTime.Now)
    Clipboard.SetData("MyTimeSpan", TimeSpan.FromMinutes(30))

    ' Later retrieval with type safety
    Dim value As TimeSpan

    If Clipboard.TryGetData("MyTimeSpan", value) Then
        Console.WriteLine($"Clipboard value is: {value}")
    End If
End Sub

Colecciones de tipos primitivos

Las matrices y listas genéricas de tipos primitivos admitidos funcionan sin configuración adicional. Sin embargo, tenga en cuenta estas limitaciones:

  • Todos los elementos de array y lista deben ser compatibles con los tipos primitivos.
  • Evite string[] y List<string> porque el formato NRBF tiene dificultades para manejar valores nulos en colecciones de cadenas.
  • Almacene cadenas individualmente o use la serialización JSON para colecciones de cadenas.

En los ejemplos siguientes se muestra cómo se pueden establecer matrices y listas en el Portapapeles:

public static void CollectionsExample()
{
    // Arrays of primitive types
    int[] numbers = { 1, 2, 3, 4, 5 };
    Clipboard.SetData("NumberArray", numbers);

    double[] coordinates = { 1.0, 2.5, 3.7 };
    Clipboard.SetData("Coordinates", coordinates);

    // Generic lists
    List<int> intList = new List<int> { 10, 20, 30 };
    Clipboard.SetData("IntList", intList);

    // Retrieval maintains type safety
    if (Clipboard.TryGetData("NumberArray", out int[] retrievedNumbers))
    {
        Console.WriteLine($"Numbers: {string.Join(", ", retrievedNumbers)}");
    }
}
Public Shared Sub CollectionsExample()
    ' Arrays of primitive types
    Dim numbers As Integer() = {1, 2, 3, 4, 5}
    Clipboard.SetData("NumberArray", numbers)

    Dim coordinates As Double() = {1.0, 2.5, 3.7}
    Clipboard.SetData("Coordinates", coordinates)

    ' Generic lists
    Dim intList As New List(Of Integer) From {10, 20, 30}
    Clipboard.SetData("IntList", intList)

    ' Retrieval maintains type safety
    Dim retrievedNumbers As Integer() = Nothing

    If Clipboard.TryGetData("NumberArray", retrievedNumbers) Then
        Console.WriteLine($"Numbers: {String.Join(", ", retrievedNumbers)}")
    End If
End Sub

Tipos de System.Drawing

Los tipos comunes de gráficos del espacio de nombres System.Drawing funcionan sin problemas con el Portapapeles y las operaciones DataObject. Estos tipos son útiles para las aplicaciones que funcionan con elementos visuales y necesitan transferir datos relacionados con el dibujo entre componentes o aplicaciones. Tenga en cuenta que la serialización de un Bitmap puede consumir una gran cantidad de memoria, especialmente para imágenes grandes. Se admiten los tipos siguientes:

  • Point, PointF, Rectangle, RectangleF.
  • Size, SizeF, Color.
  • Bitmap (puede consumir memoria significativa cuando se serializa).

En los ejemplos siguientes se muestra cómo se pueden usar estos tipos de gráficos con las operaciones del Portapapeles:

public static void SystemDrawingTypesExample()
{
    // Geometric types
    Point location = new Point(100, 200);
    Rectangle bounds = new Rectangle(0, 0, 500, 300);
    Size dimensions = new Size(800, 600);

    Clipboard.SetData("Location", location);
    Clipboard.SetData("Bounds", bounds);
    Clipboard.SetData("Size", dimensions);

    // Color information
    Color backgroundColor = Color.FromArgb(255, 128, 64, 192);
    Clipboard.SetData("BackColor", backgroundColor);

    // Bitmap data (use with caution for large images)
    Bitmap smallIcon = new Bitmap(16, 16);
    Clipboard.SetData("Icon", smallIcon);
}
Public Shared Sub SystemDrawingTypesExample()
    ' Geometric types
    Dim location As New Point(100, 200)
    Dim bounds As New Rectangle(0, 0, 500, 300)
    Dim dimensions As New Size(800, 600)

    Clipboard.SetData("Location", location)
    Clipboard.SetData("Bounds", bounds)
    Clipboard.SetData("Size", dimensions)

    ' Color information
    Dim backgroundColor As Color = Color.FromArgb(255, 128, 64, 192)
    Clipboard.SetData("BackColor", backgroundColor)

    ' Bitmap data (use with caution for large images)
    Dim smallIcon As New Bitmap(16, 16)
    Clipboard.SetData("Icon", smallIcon)
End Sub

Trabajar con tipos personalizados

Cuando se usa SetDataAsJson<T>(String, T) y TryGetData con tipos personalizados, System.Text.Json se controla la serialización automáticamente. Muchos tipos funcionan sin ninguna configuración especial: registros, clases simples y estructuras con propiedades públicas serializan sin problemas.

Tipos simples que funcionan sin atributos

Los tipos personalizados más sencillos no requieren una configuración especial:

// Records work without any attributes.
public record PersonInfo(string Name, int Age, string Email);

// Simple classes serialize all public properties automatically.
public class DocumentMetadata
{
    public string Title { get; set; }
    public DateTime Created { get; set; }
    public string Author { get; set; }
}

// Structs with public properties work seamlessly.
public struct Point3D
{
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}
' Simple classes serialize all public properties automatically.
Public Class DocumentMetadata
    Public Property Title As String
    Public Property Created As DateTime
    Public Property Author As String
End Class

' Structs with public properties work seamlessly.
Public Structure Point3D
    Public Property X As Double
    Public Property Y As Double
    Public Property Z As Double
End Structure

Uso de atributos JSON para el control avanzado

Use System.Text.Json atributos solo cuando necesite personalizar el comportamiento de serialización. Para obtener instrucciones completas sobre las System.Text.Json opciones de serialización, atributos y configuración avanzada, consulte Serialización y deserialización de JSON en .NET.

En el ejemplo siguiente se muestra cómo puede usar atributos JSON para controlar la serialización:

public class ClipboardFriendlyType
{
    // Include a field that normally isn't serialized
    [JsonInclude]
    private int _privateData;

    // Public properties are always serialized
    public string Name { get; set; }
    
    // Exclude sensitive or non-essential data
    [JsonIgnore]
    public string InternalId { get; set; }
    
    // Handle property name differences for compatibility
    [JsonPropertyName("display_text")]
    public string DisplayText { get; set; }
    
    // Control null value handling
    [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
    public string OptionalField { get; set; }
}
Public Class ClipboardFriendlyType
    ' Include a field that normally isn't serialized
    <JsonInclude>
    Private _privateData As Integer

    ' Public properties are always serialized
    Public Property Name As String

    ' Exclude sensitive or non-essential data
    <JsonIgnore>
    Public Property InternalId As String

    ' Handle property name differences for compatibility
    <JsonPropertyName("display_text")>
    Public Property DisplayText As String

    ' Control null value handling
    <JsonIgnore(Condition:=JsonIgnoreCondition.WhenWritingNull)>
    Public Property OptionalField As String
End Class

Ejemplo: Operaciones del Portapapeles con tipos personalizados

public static void CustomTypesClipboardOperationsExample()
{
    var data = new ClipboardFriendlyType 
    { 
        Name = "Sample", 
        DisplayText = "Sample Display Text",
        InternalId = "internal-123" // This property isn't serialized due to [JsonIgnore]
    };

    Clipboard.SetDataAsJson("MyAppData", data);

    if (Clipboard.TryGetData("MyAppData", out ClipboardFriendlyType retrieved))
    {
        Console.WriteLine($"Retrieved: {retrieved.Name}");
        // retrieved.InternalId is null because of [JsonIgnore]
    }
}
Public Shared Sub CustomTypesClipboardOperationsExample()
    Dim data As New ClipboardFriendlyType With {
        .Name = "Sample",
        .DisplayText = "Sample Display Text",
        .InternalId = "internal-123" ' This property isn't serialized due to <JsonIgnore>
    }

    Clipboard.SetDataAsJson("MyAppData", data)

    Dim retrieved As ClipboardFriendlyType = Nothing
    If Clipboard.TryGetData("MyAppData", retrieved) Then
        Console.WriteLine($"Retrieved: {retrieved.Name}")
        ' retrieved.InternalId is null because of <JsonIgnore>
    End If
End Sub

Precaución

BinaryFormatter La asistencia no se recomienda. Úselo solo como puente de migración temporal para aplicaciones heredadas que no pueden migrar inmediatamente a las nuevas API con seguridad de tipos.

Si debe seguir usando BinaryFormatter para las operaciones del Portapapeles en .NET 10, habilite la compatibilidad limitada a través de la configuración explícita. Este enfoque conlleva riesgos de seguridad significativos y requiere varios pasos.

Para obtener instrucciones paso a paso completas, consulte Habilitación de la compatibilidad con el Portapapeles BinaryFormatter (no recomendado). Para obtener instrucciones generales sobre la migración, consulte la guía de migración binaryFormatter.

Advertencias y riesgos de seguridad

BinaryFormatter es intrínsecamente inseguro y en desuso por estas razones:

  • Vulnerabilidades arbitrarias de ejecución de código: los atacantes pueden ejecutar código malintencionado durante la deserialización, exponiendo la aplicación a ataques remotos.
  • Ataques por denegación de servicio: los datos malintencionados del Portapapeles pueden consumir recursos excesivos de memoria o CPU, lo que provoca bloqueos o inestabilidad.
  • Riesgos de divulgación de información: los atacantes pueden extraer datos confidenciales de la memoria.
  • Sin límites de seguridad: el formato es fundamentalmente no seguro y los valores de configuración no pueden protegerlo.

Solo habilite esta compatibilidad como puente temporal mientras actualiza la aplicación para usar las nuevas API seguras para tipos.

Para obtener instrucciones de seguridad detalladas y pasos de configuración, consulte Advertencias y riesgos de seguridad en la guía paso a paso.

Implementar solucionadores de tipos enfocados en la seguridad

Incluso con BinaryFormatter habilitado, debe implementar solucionadores de tipos para restringir la deserialización a tipos aprobados explícitamente. Siga estas instrucciones:

  • Use listas de permitidos explícitas. Rechazar cualquier tipo no aprobado explícitamente.
  • Valide los nombres de tipo. Asegúrese de que los nombres de tipo coincidan exactamente con los valores esperados.
  • Restringa solo a tipos esenciales. Incluya solo los tipos necesarios para la funcionalidad del portapapeles.
  • Lance excepciones para tipos desconocidos. Rechaza claramente tipos no autorizados.
  • Revise periódicamente. Audite y actualice la lista de permitidos según sea necesario.

Para obtener ejemplos de implementación completos y ejemplos de código, consulte Implementación de solucionadores de tipos centrados en la seguridad en la guía paso a paso.

Usa IA para migrar código del portapapeles

La migración de operaciones del Portapapeles de .NET 8 a .NET 10 implica cambios de código sistemáticos en varios archivos y clases. Las herramientas de inteligencia artificial como GitHub Copilot pueden ayudar a acelerar la migración mediante la identificación de patrones heredados, la sugerencia de reemplazos modernos y la creación de escenarios de prueba. En lugar de buscar manualmente en el código base y convertir cada operación del portapapeles individualmente, puede usar IA para gestionar tareas repetitivas mientras se centra en validar los resultados y gestionar los casos límite.

En las secciones siguientes se muestran estrategias específicas de indicación para distintos aspectos de la migración del Portapapeles, desde la búsqueda de patrones de código problemáticos hasta la creación de tipos serializables JSON sólidos y suites de pruebas integrales.

Uso de IA para identificar patrones heredados del portapapeles

Use Copilot para examinar el código base y localizar las operaciones del Portapapeles que necesitan migración. Esto le ayuda a comprender el ámbito de los cambios necesarios antes de iniciar el trabajo de migración real.

Find all clipboard operations in my codebase that use GetData(), SetData() with custom objects, DataObject.GetData(), or IDataObject.GetData(). Show me the file paths and line numbers where these patterns occur.

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.

Uso de IA para convertir GetData() en TryGetData<T>()

Utiliza Copilot para convertir llamadas obsoletas GetData() al nuevo patrón seguro de tipos TryGetData<T>(). Esta conversión incluye el control de errores adecuado y elimina la conversión no segura.

Convert this GetData() clipboard code to use the new TryGetData<T>() method with proper error handling:

[paste your existing GetData() code here]

Make sure to eliminate casting and add appropriate error handling for when the data isn't available.

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.

Uso de IA para migrar SetData() a SetDataAsJson<T>()

Use Copilot para convertir el almacenamiento de objetos personalizado desde el método obsoleto SetData() al nuevo SetDataAsJson<T>() enfoque. Esto garantiza que los objetos personalizados se serialicen correctamente en el Portapapeles.

Take this SetData() clipboard code that stores custom objects:

[paste your existing SetData() code here]

Convert it to use SetDataAsJson<T>() and make the custom types JSON-serializable. Add any necessary System.Text.Json attributes if the types have complex properties.

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.

Uso de IA para crear modelos de datos serializables json

Use Copilot para diseñar tipos personalizados que funcionen sin problemas con SetDataAsJson<T>() y TryGetData<T>(). Esto incluye agregar atributos adecuados para las propiedades que necesitan un control especial.

Create a JSON-serializable version of this class for clipboard operations:

[paste your existing class definition here]

Make it work with System.Text.Json, add JsonIgnore for sensitive properties, JsonInclude for private fields that should serialize, and JsonPropertyName for any properties that need different names in JSON.

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.

Uso de IA para generar métodos de envoltorio seguros para tipos

Use Copilot para crear métodos contenedor que encapsulan las nuevas API del Portapapeles y proporcionen interfaces limpias para los tipos de datos específicos de la aplicación.

Create a type-safe clipboard wrapper class that provides methods for storing and retrieving these custom types:

[list your custom types here]

Use SetDataAsJson<T>() and TryGetData<T>() internally, include proper error handling, and add methods like SavePersonToClipboard() and TryGetPersonFromClipboard().

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.

Utilizar IA para crear pruebas completas

Utiliza Copilot para generar conjuntos de pruebas que comprueben que la migración del Portapapeles funciona correctamente, incluidas las pruebas de serialización de ida y vuelta y los escenarios de manejo de errores.

Generate comprehensive unit tests for this clipboard code:

[paste your migrated clipboard code here]

Include tests for successful round-trip serialization, handling of null values, error cases when data isn't available, and verification that the migrated code produces the same results as the original for valid scenarios.

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.

Usar IA para validar los resultados de la migración

Use Copilot para revisar el código migrado e identificar posibles problemas o áreas en las que es posible que la migración no se complete.

Review this migrated clipboard code for potential issues:

[paste your migrated code here]

Check for: missing error handling, types that might not serialize properly to JSON, performance concerns with large objects, security issues, and any remaining uses of obsolete methods.

Copilot funciona con IA, por lo que es posible que se produzcan sorpresas y errores. Para obtener más información, consulte Preguntas más frecuentes sobre el uso general de Copilot.