none
Is there any way to merge these two classes into one class to reduce duplicated code? RRS feed

  • Question

  • Dear All,

    There are two classes. The only difference between these two classes is the array size of NaemByteUnicode , one is 20 bytes and the other is 40 bytes.

    Because I need to get the size of the class, the following description is needed

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]

    Is there any way to merge these two classes into one class to reduce duplicated code?

    Thanks and Best regards,

    E-John

    namespace DynamicCharSize
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public class ElementNameByte40
        {
            public ushort Index;
            public UInt32 IP;
    
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
            public byte[] NameByteUnicode = new byte[40];
    
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
            public byte[] Attribute = new byte[6];
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] Password = new byte[8];
        }
    
    
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public class ElementNameByte20
        {
            public ushort Index;
            public UInt32 IP;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public byte[] NameByteUnicode = new byte[20];
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
            public byte[] Attribute = new byte[6];
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] Password = new byte[8];
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                ElementNameByte40 elementNameByte40 = new ElementNameByte40();
    
                int sizeElementNameByte40 = Marshal.SizeOf(typeof(ElementNameByte40));
                Console.WriteLine("sizeElementNameByte40 = {0}", sizeElementNameByte40);
    
    
                ElementNameByte20 elementNameByte20 = new ElementNameByte20();
                int sizeElementNameByte20 = Marshal.SizeOf(typeof(ElementNameByte20));
                Console.WriteLine("sizeElementNameByte20 = {0}", sizeElementNameByte20);
    
                Console.ReadLine();
            }
        }
    }


    • Edited by E-John Monday, April 29, 2019 5:34 AM
    Monday, April 29, 2019 5:31 AM

All replies

  • Hi

    Thank you for posting here.

    Based on your description, you want to merge these two classes into one class to reduce duplicated code.

    You could try the following code.

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public class ElementNameByte40:ElementNameByte
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 40)]
            public byte[] NameByteUnicode = new byte[40];
        }
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
       public class ElementNameByte20:ElementNameByte
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
            public byte[] NameByteUnicode = new byte[20];
        }
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public class ElementNameByte
        {
            public ushort Index;
            public UInt32 IP;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
            public byte[] Attribute = new byte[6];
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
            public byte[] Password = new byte[8];
    }
    

    Result:

    Best regards,

    Jack


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Monday, April 29, 2019 6:54 AM
    Moderator
  • For 2 structs I'd lean towards Jack's solution of inheritance but I'm really stuck on why you think you need the size of the array. Does the native code define 2 different native types for this structure? In general the structure can simply use an open array and then initialize it to the correct size at instantiation time. You don't need to define 2 different structures for it. Many native calls require you to pass the size of something but you can either hard code that value (for fixed size structures) or use Marshal.SizeOf in combination with looking at the actual array size. So the need for 2 types isn't really there in most cases.

    Michael Taylor http://www.michaeltaylorp3.net

    Monday, April 29, 2019 1:54 PM
    Moderator
  • Dear CoolDadTx and Jack,

    Thanks for your replies.

    Because there are two versions binary file format for my mobile device, one is 20 bytes NameByteUnicode[] and the other support 40 bytes NameByteUnicode[]. It could be done by passing the mobile device's version to constructor to get the 20 bytes or 40 bytes NameByteUnicode[] size.

    For compatible issue, I need to check the version and use the proper schema. To get the size of the class is to calculate the size read from mobile device is correct or not.

    I use calFileSize = totalNodeNumber * Marshal.SizeOf(typeof(ElementNameByte));

    I am thinking if there is any better way.

    Thanks and Best regards,

    E-John



    • Edited by E-John Tuesday, April 30, 2019 7:14 AM
    Tuesday, April 30, 2019 7:14 AM
  • Hi

    Thanks for the feedback.

    >>Is there any way to merge these two classes into one class to reduce duplicated code?

    For your initial goal, you want to merge two classes into one class to reduce duplicated code. For this, my solution has been implemented to reduce duplicated code. 

    For merging them into a class, I think this is hard to implement because the parameter to your Marshal.SizeOf method is to call the name of a class. Therefore, we can't get two sizes result by calling only one class.

    Best Regards,

    Jack


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, April 30, 2019 8:11 AM
    Moderator
  • Unfortunately you aren't going to be able to simplify your structures if you have the requirement of that array being variable size in the middle of your structure. Because you're marking the fields as sequential you cannot use inheritance (which would alter the order). You could try to use Offset instead of sequential and then move the core fields in a base type but you'd have to set the offset for the fields explicitly and I'm not sure how the marshaller behaves when offsets are combined in base and derived types. In theory it would work but it wouldn't be maintainable.

    Alternatively you could drop the marshalas on the array, set the size in the constructor and see how it marshals it. The documentation seems to indicate that arrays will be marshaled as safe arrays but I don't know that I've ever seen that behavior inside a struct. I suspect it may get marshalled as a pointer instead. But it doesn't hurt to try.

    If neither of these options work then you're stuck with separate types. If this is going to introduce complexity into your code then use a wrapper type instead. In general you don't want to be exposing "native" types anyway so creating a .NET wrapper class that internally manages the same data (or can dynamically generate the correct native structure) would be better for the .NET side anyway.


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, April 30, 2019 2:02 PM
    Moderator
  • Perhaps you could use a single class with the maximum array size you will need (40) and another variable that tells the actual number used (20 or 40). You could set that number in the constructor, then only use that number of elements for your calculations. It would mean some wastage, because the size will always be the maximum even if you are using less, but it would be just one class so maintenance would be easier.
    Wednesday, May 1, 2019 3:37 AM
  • Dear  CoolDadTx, Jack and Ante Meridian,

    Thanks for your concerns and helps about this question.

    I found there is a way to get the size of all properties in a class, refer following link.

    https://stackoverflow.com/questions/2331889/how-to-find-the-size-of-a-class-in-c-sharp

    But I don't quite understand the code, I modify the code just by debug way.

    If this way is possible, the dynamic allocated size is done by class constructor, like the snippet below

    Thanks and Best regards,

    E-John


    namespace DynamicCharSize { public class ElementName { public ushort Index; public UInt32 IP; public byte[] NameByteUnicode; // = new byte[20]; public byte[] Attribute = new byte[6]; public byte[] Password = new byte[8]; public ElementName(int version) { if (version <= 0x1963) { NameByteUnicode = new byte[20]; } else { NameByteUnicode = new byte[40]; } } } class Program { static void Main(string[] args) { ElementName eBeforeV1963 = new ElementName(0x1963); int size = GetSizeOfObject(eBeforeV1963, -1); Console.WriteLine("class size(byte unit) ElementName Before V1963 = {0}", size); ElementName eAfterV1963 = new ElementName(0x1970); size = GetSizeOfObject(eAfterV1963, -1); Console.WriteLine("class size(byte unit) ElementName After V1963 = {0}", size); Console.ReadLine(); } // refer // https://stackoverflow.com/questions/2331889/how-to-find-the-size-of-a-class-in-c-sharp /// <summary> /// Gets the size of object. /// </summary> /// <param name="obj">The object.</param> /// <param name="avgStringSize">Average size of the string.</param> /// <returns>An approximation of the size of the object in bytes</returns> public static int GetSizeOfObject(object obj, int avgStringSize = -1) { int pointerSize = IntPtr.Size; int size = 0; Type type = obj.GetType(); var info = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); foreach (var field in info) { if (field.FieldType.IsValueType) { size += System.Runtime.InteropServices.Marshal.SizeOf(field.FieldType); } else { // E-John mark, pointerSize is not needed in my application //size += pointerSize; if (field.FieldType.IsArray) { var array = field.GetValue(obj) as Array; if (array != null) { var elementType = array.GetType().GetElementType(); if (elementType.IsValueType) { // E-John added : System.ArgumentException: '類型 'System.Byte[]' 不可以當做 Unmanaged 結構來封送處理; 因此無法計算有意義的大小或位移。'???

    // if it is Byte[], just calculate the size directly if (field.FieldType.Name == "Byte[]") { size += array.Length; } else { size += System.Runtime.InteropServices.Marshal.SizeOf(field.FieldType) * array.Length; } } else { size += pointerSize * array.Length; if (elementType == typeof(string) && avgStringSize > 0) { size += avgStringSize * array.Length; } } } } else if (field.FieldType == typeof(string) && avgStringSize > 0) { size += avgStringSize; } } } return size; } } }






    • Edited by E-John Thursday, May 2, 2019 12:58 AM
    Wednesday, May 1, 2019 8:18 AM