-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
Serializing general EndPoint instances into buffers/structures to be used with low-level interop code requires instantiating a SocketAddress and copying its bytes to a separate buffer manually. This leads to 2 extra allocations: SocketAddress and the underlying buffer. This is one of the main reasons our SendTo/ReceiveFrom methods allocate a lot, see discussion in #30797.
We can workaround this by special-casing IPEndPoint (#78591), but that would introduce complexity to the Socket codebase if implemented across all sync and async variants and doesn't work with arbitrary endpoints. We can also expose SocketAddress.Buffer (#78757), but IMO it's a partial solution, since it doesn't help with the allocations.
Instead of working around our own inefficient abstractions, we should consider adding an alternative modern and efficient API to serialize arbitrary EndPoint-s into user-provided buffers. This would help simplifying and improving socket code while keeping it compatible with arbitrary endpoints.
API Proposal
namespace System.Net;
public abstract class EndPoint
{
// Returns true if the EndPoint is serializable into a linear native sockaddr_xy structure
public virtual bool TryGetSocketAddressSize(out int size);
// Existing:
// public virtual SocketAddress Serialize();
// Proposed:
public virtual void Serialize(Span<byte> socketAddress);
// Existing:
// public virtual EndPoint Create(SocketAddress socketAddress);
// Proposed:
public virtual EndPoint Create(ReadOnlySpan<byte> socketAddress);
}API Usage
if (endPoint.TryGetSocketAddressSize(out int size))
{
Span<byte> socketAddress = size < 256 ? stackalloc byte[size] : new byte[size];
endPoint.Serialize(socketAddress);
// use socketAddress
}Alternative Designs
I can't think of a significantly different design that would also delegate the buffer lifecycle management to the consumer of the API.
If we think that DnsEndpoint is the only exception that is not serializable and needs special threatment, we can expose an int Size property instead of TryGetSocketAddressSize.
Risks
Considering compatibility, the only correct way to implement TryGetSocketAddressSize(out int size) is inefficient and may regress path that uses existing 3rd party endpoints which are not updated with overloads:
public virtual bool TryGetSocketAddressSize(out int size)
{
try
{
size = this.Serialize().Size;
return true;
}
catch
{
size = default;
return false;
}
}This is not a concern if we think DnsEndPoint is the only special case that is not serializable.