如何正确继承元组,以便从 orleans 生成序列化程序是正确的?

How to properly inherit from tuples so that serializer generation from orleans is correct?

我有一个使用 netstandard2.0 作为 TargetFramework 并遵循 Nuget 包的项目:

microsoft.orleans.core -> 版本="2.2.0"
microsoft.orleans.orleanscodegenerator.build -> 版本="2.2.0"

这个项目有一个实现元组的 DTO,如下所示:

public sealed class SomeDetailsDto : Tuple<Guid, Guid>
    {
        public SomeDetailsDto(Guid firstGuidId, Guid secondGuidId)
            : base(firstGuidId, secondGuidId)
        {
        }

        public Guid firstGuidId => Item1;

        public Guid secondGuidId => Item2;
    }

此 DTO 将在 grain 方法中使用。 orleans 生成的序列化程序代码如下所示:

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(@"Orleans-CodeGenerator", @"2.0.0.0"), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute, global::Orleans.CodeGeneration.SerializerAttribute(typeof(global::SomeDetailsDto))]
    internal sealed class OrleansCodeGenSomeDetailsDtoSerializer
    {
        private readonly global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> getField0;
        private readonly global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> setField0;
        private readonly global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> getField1;
        private readonly global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid> setField1;
        public OrleansCodeGenSomeDetailsDtoSerializer(global::Orleans.Serialization.IFieldUtils fieldUtils)
        {
            global::System.Reflection.FieldInfo field0 = typeof(global::System.Tuple<global::System.Guid, global::System.Guid>).GetField(@"m_Item1", (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public));
            getField0 = (global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetGetter(field0);
            setField0 = (global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetReferenceSetter(field0);
            global::System.Reflection.FieldInfo field1 = typeof(global::System.Tuple<global::System.Guid, global::System.Guid>).GetField(@"m_Item2", (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public));
            getField1 = (global::System.Func<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetGetter(field1);
            setField1 = (global::System.Action<global::System.Tuple<global::System.Guid, global::System.Guid>, global::System.Guid>)fieldUtils.GetReferenceSetter(field1);
        }

        [global::Orleans.CodeGeneration.CopierMethodAttribute]
        public global::System.Object DeepCopier(global::System.Object original, global::Orleans.Serialization.ICopyContext context)
        {
            global::SomeDetailsDto input = ((global::SomeDetailsDto)original);
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordCopy(original, result);
            setField0(result, getField0(input));
            setField1(result, getField1(input));
            return result;
        }

        [global::Orleans.CodeGeneration.SerializerMethodAttribute]
        public void Serializer(global::System.Object untypedInput, global::Orleans.Serialization.ISerializationContext context, global::System.Type expected)
        {
            global::SomeDetailsDto input = (global::SomeDetailsDto)untypedInput;
            context.SerializeInner(getField0(input), typeof(global::System.Guid));
            context.SerializeInner(getField1(input), typeof(global::System.Guid));
        }

        [global::Orleans.CodeGeneration.DeserializerMethodAttribute]
        public global::System.Object Deserializer(global::System.Type expected, global::Orleans.Serialization.IDeserializationContext context)
        {
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordObject(result);
            setField0(result, (global::System.Guid)context.DeserializeInner(typeof(global::System.Guid)));
            setField1(result, (global::System.Guid)context.DeserializeInner(typeof(global::System.Guid)));
            return (global::SomeDetailsDto)result;
        }
    }

一切都很好。

但最近我将 TargetFramework 更新为 netstandard2.1,microsoft.orleans.core 更新为“3.0.2”,而不是 microsoft.orleans.orleanscodegenerator.build (2.2.0 ),我安装了 Microsoft.Orleans.CodeGenerator.MSBuild (3.0.2)。

通过上述设置,我收到以下警告:
警告 ORL1001:类型 SomeDetailsDto 具有属于引用程序集的基类型。此类型的序列化程序生成可能不包含重要的基本类型字段。

恢复、构建和发布命令都在工作。但是,生成的序列化程序有问题,因为测试失败。
下面是奥尔良为同一个 DTO

生成的代码
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("OrleansCodeGen", "2.0.0.0"), global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute, global::Orleans.CodeGeneration.SerializerAttribute(typeof(global::SomeDetailsDto))]
    internal sealed class OrleansCodeGenSomeDetailsDtoSerializer
    {
        public OrleansCodeGenSomeDetailsDtoSerializer(global::Orleans.Serialization.IFieldUtils fieldUtils)
        {
        }

        [global::Orleans.CodeGeneration.CopierMethodAttribute]
        public object DeepCopier(object original, global::Orleans.Serialization.ICopyContext context)
        {
            global::SomeDetailsDto input = ((global::SomeDetailsDto)original);
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordCopy(original, result);
            return result;
        }

        [global::Orleans.CodeGeneration.SerializerMethodAttribute]
        public void Serializer(object untypedInput, global::Orleans.Serialization.ISerializationContext context, global::System.Type expected)
        {
            global::SomeDetailsDto input = (global::SomeDetailsDto)untypedInput;
        }

        [global::Orleans.CodeGeneration.DeserializerMethodAttribute]
        public object Deserializer(global::System.Type expected, global::Orleans.Serialization.IDeserializationContext context)
        {
            global::SomeDetailsDto result = (global::SomeDetailsDto)global::System.Runtime.Serialization.FormatterServices.GetUninitializedObject(typeof(global::SomeDetailsDto));
            context.RecordObject(result);
            return (global::SomeDetailsDto)result;
        }
    }

最新的序列化程序忽略了警告中提到的元组的 Item1 和 Item2。我在这个问题上搜索了很多,但没有找到任何东西。我可以使用 this orleans 文档中提到的自定义序列化程序。但是我有另一个 DTO,它也收到了前面提到的警告。

解决此问题的更好方法是什么?

我建议不要继承自Tuple。相反,将这些字段放在 class 上,这样 class 看起来像这样:

[Serializable]
public sealed class SomeDetailsDto
{
  public SomeDetailsDto(Guid firstGuidId, Guid secondGuidId)
  {
    FirstGuidId = firstGuidId;
    SecondGuidId = secondGuidId;
  }

  public Guid FirstGuidId { get; }

  public Guid SecondGuidId { get; }
}

请注意,我还向您的 class 添加了 [Serializable] 属性。我还建议您始终将 [Serializable] 添加到您打算序列化的类型。代码生成器将尽最大努力推断你想要为该类型生成一个序列化程序(因为它出现在 grain 接口方法签名和其他启发式方法中),但该过程不能完全准确 - 所以最好是明确的.

如果你想要一些通用类型表明你的 class 有 2 个字段(假设这对你有用),那么你可以定义一个接口或你自己的类似元组的类型并在你的 DTO。

正如构建警告消息指出的那样,Tuple 是在称为 ref-only 程序集的特殊类型程序集中定义的。这意味着 Orleans 代码生成器无法看到 Tuple class 中的任何字段(那些仅引用程序集省略了私有成员和所有 IL 代码,因此它们不存在于全部)。在不知道基 class 上存在哪些字段的情况下,代码生成器无法为其生成正确的序列化程序。这是由仅引用程序集引起的限制,并且在 Orleans 存储库中已经讨论了如何纠正此限制。

这里有一个支持继承自 Tuple 和集合类型的功能请求:https://github.com/dotnet/orleans/issues/6158