【问题标题】:How to restrict derived class to only have members of certain types如何将派生类限制为仅具有某些类型的成员
【发布时间】:2019-12-19 11:35:24
【问题描述】:

我需要强制从基类派生的类的成员只具有某种类型。

例如,如果我有一个 MustInherit Class Serializable,它有一个方法 ToByteArray()。 如何将派生类限制为仅具有特定类型的字段(例如,限制为具有 int、char 或 byte 类型的字段),以便保证能够使用我编写的 ToByteArray 方法序列化类.

这样做的原因是因为我希望任何派生类在序列化时都具有常量字节表示。

例如:

class Foo
    Inherits Serializable

'Valid field as it is a char guaranteed to be two bytes
Public field1 As Char

'Valid field as it is a Int which is guaranteed to be two bytes
Public field2 As Integer

'**Invalid as a string can be a dynamic length which is not wanted in this context
Public field3 As String 
End Class

.net 中是否可能有此限制? vb.net 但我对两者都很熟悉,可以在两者之间进行转换。

我的一个想法是在使用反射遍历字段并将它们的类型与有效类型列表进行比较之后,在类Serializable 的构造函数中引发运行时错误。然而,这是一个弱解决方案,因为无效类只会在运行时检测到,而不是在编译时检测到。

这是一个 SO question,它解决了同样的问题,但在 C++ 而不是 C# 中(如果需要,仅用于指导所需行为) How to enforce derived class members on derived classes

既然我已经学习了单元测试,请在 2020 年 9 月 2 日更新: 一个非常简单的解决方案是在 UnitTest 中使用反射

<TestMethod()>
Public Sub ShouldObeyTypeConstraintsTest()
    Dim types As IEnumerable(Of Type) = From t In Assembly.GetAssembly(GetType(IRestricted)).GetTypes() 
                        From i In t.GetInterfaces() 
                        Where i.IsGenericType AndAlso (i.GetGenericTypeDefinition() Is GetType(IRestricted(Of ))) 
                        Select t

    For Each type As Type In types
        Dim info() As FieldInfo = serializable.GetFields()

        For Each field In info
            Dim fieldType As Type = field.FieldType()
            If Not isValidType(fieldType) Then
                Assert.Fail($"IRestricted type {serializable.FullName} has invalid field {fieldType.Name}")
            End If
        Next
    Next
End Sub

【问题讨论】:

  • 问题不清楚。目前尚不清楚继承适用于何处。如果它不是继承类,那会是一个不同的问题吗?这可能在您的问题中有所描述,但如果是,则很难找到。另外,“只有值类型的成员” - 这是什么意思?只有返回值类型的成员?只有将值类型作为参数的成员?成员不能成为值类型,因此很难说出这意味着什么。
  • 你能给我们举个例子吗?你说的不清楚。为什么不使用 MyClassSerializable 和 IMyClassNotSerialable 将基类拆分为多个接口,并在需要时从其中一个继承?
  • 正如其他人所说,不清楚您要问的是什么。根据您引用的问题中给出的答案,您是否在寻找generics
  • @ScottHannen 感谢你们的意见!这是我第一次在这里发布问题。我重新设计了这个问题,试图更清楚。让我知道是否还有其他需要更改的地方。
  • 因此,可以在运行时进行一些反射,在单元测试期间(再次使用反射)或在编译时(如果您想花时间编写自定义分析器)。所有这些都可能是多余的,您可能应该首先考虑为什么需要它。

标签: c# .net vb.net inheritance


【解决方案1】:

不,您所描述的不可能。没有编译时约束可以放置在一个类上,该约束指示它可以具有哪些类型的成员。

如果您要实现此自定义ToByteArray 方法,一种解决方案是使用指示哪些成员已序列化或未序列化的属性。已经有一个属性可以确定二进制序列化中包含哪些字段。那是NonSerializedAttribute

在您的ToByteArray() 方法中,当您反映各个字段时,您将检查每个字段以查看它是否具有该属性,如果有则忽略它。 (我假设您正在使用反射,否则该方法将指定要包含哪些字段,并且问题将不存在。)

如果您要使用现有属性,您可能只想考虑使用现有的二进制序列化而不是编写自己的ToByteArray 方法。您可以使用BinaryFormatter 类。然后您可以使用相同的属性来防止序列化某些字段。

这些都没有让你在编译时确定类型实际上可以被序列化。因为序列化将涉及类型(而不是依赖于特定值的逻辑),所以在您测试时会出现任何序列化问题。您甚至可以编写单元测试来创建和序列化每个类的实例并防止运行时错误。

【讨论】:

  • 你说得对,我正在使用反射,没有它,问题就不会存在,因为我会有更多关于字段的信息。单元测试是个好主意。谢谢!
【解决方案2】:

您可以将多个override methods 与您希望的类型使用其中一个来设置MyField 的值。

通过这种方式,您可以确定MyField 类型是您指定的类型之一。

using System;

public class Program
{
    public static void Main()
    {
        var b = new Book();
        b.SetMyField(1);
        Console.WriteLine(b.MyField);
        b.SetMyField('A');
        Console.WriteLine(b.MyField);
    }
}


public interface IMySerializable {
    object MyField { get; }
    void SetMyField( int val);
    void SetMyField( char val);
    void SetMyField( byte val);
}

public class Book : IMySerializable {

    public object MyField { 
        get; 
        private set;
    }

    public void SetMyField( int val){
        MyField=val;
    }

    public void SetMyField( char val){
        MyField=val;
    }

    public void SetMyField( byte val){
        MyField=val;
    }
}

不幸的是你不能在通用classwhere条件中指定一些类型,如intcharC#中的method,否则你会收到错误像这样:


也可以尝试使用ISerializable 接口。 Here有一篇关于它的文章。

【讨论】:

  • 有趣!但是,对于未知数量的字段,这是否可能?
  • @PhillipS,您似乎正在为您的字段寻找 T 类型(通用类型),然后您想限制 T 支持的类型类型。我更新了我的答案,请阅读我的结尾答案是因为在 C# 中,关于指定泛型类型的支持类型存在一些限制。另请阅读this Q/A
  • aha 所以如果我对诸如 where T: Struct 之类的约束感到满意,那么它对通用字段是可行的(我不知道通用 Fields 是一回事! ),但由于我们不能施加更广泛的限制,这还不够。
猜你喜欢
  • 2019-11-13
  • 1970-01-01
  • 1970-01-01
  • 2017-12-02
  • 1970-01-01
  • 2017-01-19
  • 1970-01-01
  • 2013-06-03
  • 1970-01-01
相关资源
最近更新 更多