-1

I need to enforce the members of a class derived from a base class to only have a certain type.

For example if I have a MustInherit Class Serializable which has a method ToByteArray(). How can I restrict derived classes to only have fields which are specific types (for example restricted to having fields of type int, char or byte) so that I'm guaranteed to be able to serialize the class with the ToByteArray method which I have written.

The reason for this is because I want any derived classes to have a constant byte representation when it is serialized.

For example:

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

Is this restriction possible in .net? vb.net in specific however I'm familiar with both and can convert between the two.

An idea I had was to throw a run-time error in the class Serializable's constructor after looping through the fields using Reflection and comparing their types against a list of valid types. However this a is a weak solution as an invalid class would only be detected during run-time, rather than compile time.

Here is a SO question which tackles the same problem but is in C++ not C# (just for guidance as to the desired behavior if needed) How to enforce derived class members on derived classes

Update 9/2/2020 now that I've learned Unit Tests: Quite a simple solution is to use Reflection in a 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
Phillip
  • 631
  • 6
  • 15
  • The question isn't clear. It's not unclear where inheritance fits in. Would it be a different question if it wasn't an inherited class? That might be described in your question, but if it is, it's hard to find. Also, "to only have members which are value types" - what does that mean? Only members that return value types? Only members that take value types as parameters? A member cannot *be* a value type so it' hard to tell what that means. – Scott Hannen Aug 12 '19 at 22:14
  • Can you give us an example? What you're saying isn't clear. Why don't you split up the base class into multiple interfaces with MyClassSerializable and IMyClassNotSerialable , and just inherit from one when you need it? – Train Aug 12 '19 at 22:16
  • As others have said it isn't clear what it is that you're asking. Based on the answers given in the question you referenced, are you looking for [generics](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/)? – Joshua Robinson Aug 12 '19 at 22:24
  • @ScottHannen Thanks for your guys's input! It's my first time posting a question on here. I reworked the question in an attempt to be more clear. Let me know if there's anything else that needs changing. – Phillip Aug 12 '19 at 22:38
  • 1
    So this is possible to do, either at runtime with some reflection, during unit testing (again with reflection) or at compile time if you want to spend the time to write a custom analyser. All of those are probably overkill and you should probably be thinking why it's needed in the first place. – DavidG Aug 13 '19 at 07:48
  • A custom code analyzer at compile time or live code analysis is a good idea! – Phillip Aug 13 '19 at 21:04

2 Answers2

1

You can use several override methods with the types you want to set the value of the MyField just by one of them.

In this way you can be sure the MyField type is one of your specified types.

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;
    }
}

Unfortunately you can not specify some types like int, char in the where condition of a generic class or method in C# else you get an error like this:

enter image description here


Also try using the ISerializable interface. Here there is an article about it.

Ramin Bateni
  • 16,499
  • 9
  • 69
  • 98
  • Interesting! However, would this be possible with an unknown number of fields? – Phillip Aug 12 '19 at 22:56
  • @PhillipS, it seems you are looking for a T type (a generic type) for your fields and then you want limit the type of supported types for T. I updated my answer please read end of my answer because In C# there are some limitations about specifying supported types of a generic type. Also read [this Q/A](https://stackoverflow.com/questions/8552863/generic-method-where-t-is-type1-or-type2) – Ramin Bateni Aug 12 '19 at 23:02
  • aha so if I was happy with a constraint such as where T: Struct then it would be doable with the Generic fields (I had no idea generic *Fields* were a thing!), but since we cannot impose more extensive limitations this would not be sufficient. – Phillip Aug 12 '19 at 23:24
1

No, what you're describing is not possible. There is no compile-time constraint you can place on a class that dictates what types of members it can have.

If you're going to implement this custom ToByteArray method, one solution would be to use attributes that indicate which members are or are not serialized. There's already an attribute for that to determine which fields are included in binary serialization. That's the NonSerializedAttribute.

In your ToByteArray() method, as you're reflecting the various fields, you would check each one to see if it has the attribute, and ignore it if it does. (I'm assuming that you're using reflection, because otherwise the method would specify which fields to include, and the problem wouldn't exist.)

If you're going to use the existing attribute, you might want to just consider using existing binary serialization instead of writing your own ToByteArray method. You can use the BinaryFormatter class. Then you can use the same attribute to prevent serializing certain fields.

What none of this gets you is compile-time certainty that the type can actually be serialized. Because serialization will involve types (not logic that depends on particular values) any serialization issues would appear as you test. You could even write unit tests that create and serialize instances of each class and that would prevent runtime errors.

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • You're exactly right about the fact that I'm using reflection and without it the problem wouldn't exist since I would have more information about the fields. The unit testing is a good idea. Thanks! – Phillip Aug 12 '19 at 23:48