Loading...
Loading...
Compare original and translation side by side
| Type | Definition | Scope |
|---|---|---|
| API/Source | Code compiles against newer version | Public method signatures, types |
| Binary | Compiled code runs against newer version | Assembly layout, method tokens |
| Wire | Serialized data readable by other versions | Network protocols, persistence formats |
| 类型 | 定义 | 范围 |
|---|---|---|
| API/源代码 | 代码可针对新版本编译 | 公共方法签名、类型 |
| 二进制 | 已编译代码可在新版本上运行 | 程序集布局、方法令牌 |
| 有线 | 序列化数据可被其他版本读取 | 网络协议、持久化格式 |
// ADD new overloads with default parameters
public void Process(Order order, CancellationToken ct = default);
// ADD new optional parameters to existing methods
public void Send(Message msg, Priority priority = Priority.Normal);
// ADD new types, interfaces, enums
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }
// ADD new members to existing types
public class Order
{
public DateTimeOffset? ShippedAt { get; init; } // NEW
}// ADD new overloads with default parameters
public void Process(Order order, CancellationToken ct = default);
// ADD new optional parameters to existing methods
public void Send(Message msg, Priority priority = Priority.Normal);
// ADD new types, interfaces, enums
public interface IOrderValidator { }
public enum OrderStatus { Pending, Complete, Cancelled }
// ADD new members to existing types
public class Order
{
public DateTimeOffset? ShippedAt { get; init; } // NEW
}// REMOVE or RENAME public members
public void ProcessOrder(Order order); // Was: Process()
// CHANGE parameter types or order
public void Process(int orderId); // Was: Process(Order order)
// CHANGE return types
public Order? GetOrder(string id); // Was: public Order GetOrder()
// CHANGE access modifiers
internal class OrderProcessor { } // Was: public
// ADD required parameters without defaults
public void Process(Order order, ILogger logger); // Breaks callers!// REMOVE or RENAME public members
public void ProcessOrder(Order order); // Was: Process()
// CHANGE parameter types or order
public void Process(int orderId); // Was: Process(Order order)
// CHANGE return types
public Order? GetOrder(string id); // Was: public Order GetOrder()
// CHANGE access modifiers
internal class OrderProcessor { } // Was: public
// ADD required parameters without defaults
public void Process(Order order, ILogger logger); // Breaks callers!// Step 1: Mark as obsolete with version (any release)
[Obsolete("Obsolete since v1.5.0. Use ProcessAsync instead.")]
public void Process(Order order) { }
// Step 2: Add new recommended API (same release)
public Task ProcessAsync(Order order, CancellationToken ct = default);
// Step 3: Remove in next major version (v2.0+)
// Only after users have had time to migrate// Step 1: Mark as obsolete with version (any release)
[Obsolete("Obsolete since v1.5.0. Use ProcessAsync instead.")]
public void Process(Order order) { }
// Step 2: Add new recommended API (same release)
public Task ProcessAsync(Order order, CancellationToken ct = default);
// Step 3: Remove in next major version (v2.0+)
// Only after users have had time to migratedotnet add package PublicApiGenerator
dotnet add package Verify.Xunit[Fact]
public Task ApprovePublicApi()
{
var api = typeof(MyLibrary.PublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}ApprovePublicApi.verified.txtnamespace MyLibrary
{
public class OrderProcessor
{
public OrderProcessor() { }
public void Process(Order order) { }
public Task ProcessAsync(Order order, CancellationToken ct = default) { }
}
}dotnet add package PublicApiGenerator
dotnet add package Verify.Xunit[Fact]
public Task ApprovePublicApi()
{
var api = typeof(MyLibrary.PublicClass).Assembly.GeneratePublicApi();
return Verify(api);
}ApprovePublicApi.verified.txtnamespace MyLibrary
{
public class OrderProcessor
{
public OrderProcessor() { }
public void Process(Order order) { }
public Task ProcessAsync(Order order, CancellationToken ct = default) { }
}
}*.verified.txt*.verified.txt| Direction | Requirement |
|---|---|
| Backward | Old writers → New readers (current version reads old data) |
| Forward | New writers → Old readers (old version reads new data) |
| 方向 | 要求 |
|---|---|
| 向后兼容 | 旧写入端 → 新读取端(当前版本可读取旧数据) |
| 向前兼容 | 新写入端 → 旧读取端(旧版本可读取新数据) |
// New message type - readers deployed first
public sealed record HeartbeatV2(
Address From,
long SequenceNr,
long CreationTimeMs); // NEW field
// Deserializer handles both old and new
public object Deserialize(byte[] data, string manifest) => manifest switch
{
"Heartbeat" => DeserializeHeartbeatV1(data), // Old format
"HeartbeatV2" => DeserializeHeartbeatV2(data), // New format
_ => throw new NotSupportedException()
};// Config to enable new format (off by default initially)
akka.cluster.use-heartbeat-v2 = on// New message type - readers deployed first
public sealed record HeartbeatV2(
Address From,
long SequenceNr,
long CreationTimeMs); // NEW field
// Deserializer handles both old and new
public object Deserialize(byte[] data, string manifest) => manifest switch
{
"Heartbeat" => DeserializeHeartbeatV1(data), // Old format
"HeartbeatV2" => DeserializeHeartbeatV2(data), // New format
_ => throw new NotSupportedException()
};// Config to enable new format (off by default initially)
akka.cluster.use-heartbeat-v2 = on| Format | Type | Wire Compatibility |
|---|---|---|
| Protocol Buffers | Schema-based | Excellent - explicit field numbers |
| MessagePack | Schema-based | Good - with contracts |
| System.Text.Json | Schema-based (with source gen) | Good - explicit properties |
| Newtonsoft.Json | Reflection-based | Poor - type names in payload |
| BinaryFormatter | Reflection-based | Terrible - never use |
dotnet/serialization| 格式 | 类型 | 有线兼容性 |
|---|---|---|
| Protocol Buffers | 基于Schema | 极佳 - 明确的字段编号 |
| MessagePack | 基于Schema | 良好 - 配合契约 |
| System.Text.Json | 基于Schema(配合源代码生成) | 良好 - 明确的属性 |
| Newtonsoft.Json | 基于反射 | 较差 - 负载中包含类型名称 |
| BinaryFormatter | 基于反射 | 极差 - 绝不使用 |
dotnet/serialization// Attribute for documentation
[InternalApi]
public class ActorSystemImpl { }
// Namespace convention
namespace MyLibrary.Internal
{
public class InternalHelper { } // Public for extensibility, not for users
}Types innamespaces or marked with.Internalmay change between any releases without notice.[InternalApi]
// Attribute for documentation
[InternalApi]
public class ActorSystemImpl { }
// Namespace convention
namespace MyLibrary.Internal
{
public class InternalHelper { } // Public for extensibility, not for users
}位于命名空间或标记有.Internal的类型可能在任意版本间变更,恕不另行通知。[InternalApi]
// DO: Seal classes not designed for inheritance
public sealed class OrderProcessor { }
// DON'T: Leave unsealed by accident
public class OrderProcessor { } // Users might inherit, blocking changes// DO: Seal classes not designed for inheritance
public sealed class OrderProcessor { }
// DON'T: Leave unsealed by accident
public class OrderProcessor { } // Users might inherit, blocking changes// DO: Small, focused interfaces
public interface IOrderReader
{
Order? GetById(OrderId id);
}
public interface IOrderWriter
{
Task SaveAsync(Order order);
}
// DON'T: Monolithic interfaces (can't add methods without breaking)
public interface IOrderRepository
{
Order? GetById(OrderId id);
Task SaveAsync(Order order);
// Adding new methods breaks all implementations!
}// DO: Small, focused interfaces
public interface IOrderReader
{
Order? GetById(OrderId id);
}
public interface IOrderWriter
{
Task SaveAsync(Order order);
}
// DON'T: Monolithic interfaces (can't add methods without breaking)
public interface IOrderRepository
{
Order? GetById(OrderId id);
Task SaveAsync(Order order);
// Adding new methods breaks all implementations!
}| Version | Changes Allowed |
|---|---|
| Patch (1.0.x) | Bug fixes, security patches |
| Minor (1.x.0) | New features, deprecations, obsolete removal |
| Major (x.0.0) | Breaking changes, old API removal |
| 版本 | 允许的变更 |
|---|---|
| 补丁版本 (1.0.x) | Bug修复、安全补丁 |
| 次要版本 (1.x.0) | 新功能、弃用标记、移除已过时内容 |
| 主版本 (x.0.0) | 破坏性变更、旧API移除 |
[Obsolete][Obsolete]Before removing or changing something, understand why it exists.
在移除或修改某事物之前,先理解它存在的原因。
[Obsolete].verified.txt[Obsolete].verified.txt// "Bug fix" that breaks users
public async Task<Order> GetOrderAsync(OrderId id) // Was sync!
{
// "Fixed" to be async - but breaks all callers
}
// Correct: Add new method, deprecate old
[Obsolete("Use GetOrderAsync instead")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;
public async Task<Order> GetOrderAsync(OrderId id) { }// "Bug fix" that breaks users
public async Task<Order> GetOrderAsync(OrderId id) // Was sync!
{
// "Fixed" to be async - but breaks all callers
}
// Correct: Add new method, deprecate old
[Obsolete("Use GetOrderAsync instead")]
public Order GetOrder(OrderId id) => GetOrderAsync(id).Result;
public async Task<Order> GetOrderAsync(OrderId id) { }// Changing defaults breaks users who relied on old behavior
public void Configure(bool enableCaching = true) // Was: false!
// Correct: New parameter with new name
public void Configure(
bool enableCaching = false, // Original default preserved
bool enableNewCaching = true) // New behavior opt-in// Changing defaults breaks users who relied on old behavior
public void Configure(bool enableCaching = true) // Was: false!
// Correct: New parameter with new name
public void Configure(
bool enableCaching = false, // Original default preserved
bool enableNewCaching = true) // New behavior opt-in// AVOID: Type names in wire format
{ "$type": "MyApp.Order, MyApp", "Id": 123 }
// Renaming Order class = wire break!
// PREFER: Explicit discriminators
{ "type": "order", "id": 123 }// AVOID: Type names in wire format
{ "$type": "MyApp.Order, MyApp", "Id": 123 }
// Renaming Order class = wire break!
// PREFER: Explicit discriminators
{ "type": "order", "id": 123 }