How to Fix Enum Error in ProtoBuf-Net - c#

I am getting this error when trying to serialize.
The answer to this question:
How to map System Enum's in Protobuf.Net
indicates that this is related to a Flags Enum and that it should be handled in V2. The Enum being reported here is not a Flags Enum:
public enum RunwayDesignator {
NONE = 0,
LEFT = 1,
RIGHT = 2,
CENTER = 3,
WATER = 4,
C = 5,
L = 6,
R = 7,
W = 8,
A = 9,
B = 10,
NOT_APP = 99
}
I assume the '16' refers to something in the Enum although there are not 16 values. I checked also to see if there are any ProtoMember IDs of 16 related to unsages of this enum - there are not. All usages of this enum that are serialized are private fields.
I would appreciate some guidance on how to deal with this.
Mant Thanks

Well - this is embarrassing. The problem is that the value of 16 is indeed being generated. So it looks like this is some sort of programming error on my part. The error message is saying there is no value for 16 in the enum and that is true.
So I can now go back and try and fix my code. Protobuf-Net is nowhere at fault.
I guess this might be useful for others who see this error. Find out where the enum value is being used and see if the code is sending an invalid value. What I don't understand is why I am not seeing some sort of runtime error when trying to set an invalid index for the enum. I need to investigate that now. And here is an answer to that
Why does casting int to invalid enum value NOT throw exception?
It seems that there is no error generated for invalid enum values but protobuf-net does find them

Related

How to retrieve numeric data as string

This kind question may be asked before, but I can't get my head around it, so I'll appreciate any help.
I have a message box where a type of error that i'm receiving by modbus communication is stored.
MessageBox.Show(String.Format("Error uploading: {0}", e.ReceivedData[0]));
e.ReceivedData[0] is holding the data that I want to show on the message box.
So, it might be 1 2 3 4 5 6 7. Those numbers represent type of operation failure.
However, I want to show the exact error message
1 - "UR_NOT_ACTIVE",
2 - "UR_OUT_OF_BOUND",
3 - "UR_INVALID_COMMAND"
and so on.
My plan was to store it in an enum, but I have no previous experience with c#, so it it more confusing than I thought.
Yes, use an enum:
public enum OperationFailureType
{
UR_NOT_ACTIVE = 1,
UR_OUT_OF_BOUND = 2,
UR_INVALID_COMMAND = 3
}
Now you can use enumValue.ToString to get the text and you can cast it to int to get the number.
MessageBox.Show($"Error uploading: {e.ReceivedData[0].ToString()}");
Presuming ReceivedData is now an array of this enum. If it's just an int[] and you can't change it you can cast it to the OperationFailureType:
OperationFailureType failureType = (OperationFailureType) e.ReceivedData[0];
MessageBox.Show($"Error uploading: {failureType.ToString()}");

Protobuf-net deserialization exception with enum

I have an enum with flags that I decorate with the [ProtoMember] attribute that serializes and deserializes fine on my local box running Win7 x64.
However my use case involves serializing on a server running Windows Server 2008 R2 Enterprise 64-bit and deserializing on my local box. When I deserialize, I get the exception:"Overflow Exception was unhandled; Arithmetic operation resulted in an overflow". It seems to be thrown from ProtoBuf.Serializers.CompiledSerializer.ProtoBuf.Serializers.IProtoSerializer.Read(Object value, ProtoReader source).
I tried changing the enum to an int and serializing on server/deserializing locally works. I would like to use the enum instead of an int. What am I doing wrong?
Not sure if this is pertinent information but the executable I run on the server is built on my local box.
The enum is from a referenced external dll. When I duplicate the enum code in my solution, deserializing works. The exception is only thrown when I am using an enum from an external dll (where I suspect the source code isn't known) and the enum value is larger than (it seems) 128. In my case, Status.Zeta and Status.All threw the exception; other enum values deserialized properly. The enum is defined as such:
[Flags]
public enum Status
{
None = 0,
Alpha = 1,
Beta = 8,
Gamma = 16,
Delta = 32,
Epsilon = 64,
Zeta = 132,
All = 255,
}
I cannot change the code in the dll. How can I make this work? Do I need a .proto file? I am trying to avoid this if possible.
This only impacts enums that are : byte
D'oh! Spot the brain-dead error:
case ProtoTypeCode.SByte: Emit(OpCodes.Conv_Ovf_U1); break;
case ProtoTypeCode.Byte: Emit(OpCodes.Conv_Ovf_I1); break;
This will be reversed and deployed later today. Thanks.
To explain properly: byte is unsigned (0 to 255), sbyte is signed (-128 to 127); Conv_Ovf_U1 is basically IL for "convert to byte, checking for overflow" (like how the checked keyword in C# works), and Conv_Ovf_I1 is "convert to sbyte, checking for overflow". Hence any value over 127 was triggering the overflow flag, causing an exception. This is fixed in r614, now deployed.
That is indeed a bit strange. There could be differences in the CLR that affect ProtoBuf (for instance, the CLR ships with a number of different GCs). Comparing the Machine.config files from the two machines might expose some differences.
As for solving the problem, you could try marking the enum itself with ProtoContract and each enum member with ProtoMember. The latter allows you to set a Value property for ProtoBuf to use. You can also set the DataFormat to Fixed and see if that works better than the default.
You can find some examples here.

C# Flags parsing

I have wrote enum:
[Flags]
public enum BundleOS {
Win32 = 0,
Win64 = 1,
Mac = 2
}
I need parse it from string, and write to string. Sample of string: "Win32|Win64".
Next code returns invalid result:
BundleOS os;
Boolean result = TryParse<BundleOS>("Win32|Win64", out os);
In result variable I got the false value. But I need true, and os value must to have the BundleOS.Win32|BundleOS.Win64 value.
If I do such operation:
String x = (BundleOS.Win32|BundleOS.Win64).ToString();
I need get such value: "Win32|Win64", but I get "Win64".
Is exists a simple solution of these problems?
Your problem is that your performing a bitwise operation and Win32 equals 0.
So Win64 OR Win32 is actually Win64 OR 0 which returns Win64.
You can set your enum like this:
[Flags]
public enum BundleOS
{
Win32 = 1,
Win64 = 2,
Mac = 4
}
On a side note:
I'll also point out a very good question that was asked earlier this week on how to define flag enums.
In addition to the answer given by #Blachshma regarding your particular flags, if you want to take the string form of "Win32|Win64" and turn it into an actual instance of your enum, you've got a bit more work cut out.
First you'll simply need to split() the string by the '"|"' in order to get the individual values.
Then you can use Enum.GetNames() and Enum.GetValues() to get a list of the names and values for elements in your original enum. You can then loop through the split components, and find the matching entry (and its value) from your original enum.
Try this to parse your flags string. I havn't tested but it should get you started:
BundleOS flags = "Win32|Win64"
.Split('|')
.Select(s => (BundleOS)Enum.Parse(typeof(BundleOs), s))
.Aggregate((f, i) => f|i);
In addition to Blachshma's answer, the MSDN documentation for the FlagsAttribute actually explains very succinctly in the "Guidelines for FlagsAttribute and Enum" section:
Define enumeration constants in powers of two, that is, 1, 2, 4, 8,
and so on. This means the individual flags in combined enumeration
constants do not overlap.
Use None as the name of the flag enumerated constant whose value is
zero. You cannot use the None enumerated constant in a bitwise AND
operation to test for a flag because the result is always zero.
However, you can perform a logical, not a bitwise, comparison between
the numeric value and the None enumerated constant to determine
whether any bits in the numeric value are set.

Enum.GetNames() results in unexpected order with negative enum constants

I have the following enum definition (in C#):
public enum ELogLevel
{
General = -1, // Should only be used in drop-down box in Merlinia Administrator log settings
All = 0, // Should not be used as a level, only as a threshold, effectively same as Trace
Trace = 1,
Debug = 2,
Info = 3,
Warn = 4,
Error = 5,
Fatal = 6,
Off = 7 // Should not be used as a level, only as a threshold
}
Now, when I do an Enum.GetNames() on this type I get a string array with 9 elements as expected, but the order is All, Trace, ... , Off, General, which is not what I was expecting.
Here's the MSDN documentation for Enum.GetNames():
"Remarks: The elements of the return value array are sorted by the
values of the enumerated constants."
What's going on here? I can change my program to take this "functionality" into account, but I'd kind of like to know why .NET is doing what it's doing.
This is a known bug with both GetNames() and GetValues() that was reported here, but ended up getting closed as won't fix:
Yes, this method indeed has a bug where it returns the array of enum values sorted as unsigned-types (-2 is 0xFFFFFFFE and -1 is 0xFFFFFFFF in two's complement, that's why they are showing up at the end of the list) instead of returning values sorted by their signed-types.
Unfortunately, we cannot change the sort order of GetValues because we will break all existing .NET programs that have been written to depend on the current sorting behavior [...]
Looks like you'll have to reorder the values yourself.
Depending on how the sorting occurs, it may be that it is sorting the values as if they were unsigned, in which case, -1 = 0xffffffff, which is of course greater than 7.

Using hashcode to generate value for enum

As we know, all enums are compiled as constants, which means you can get unexpected results if you use an enum from a different assembly.
Settings sequential enum numbers explicitly is no help, because if you want to leave room for new values, you have to do basic-linenumber-style spacing, which is naff.
So I wondered about using the hashcode of the Enum value name as string, which is fairly easy to generate a script for.
Is this a good idea? Will I have problems with the negative values that hashcode can return?
EDIT
For those asking why, I quote the linked "corner case":
Given an assembly with:
enum MyEnum
{
Red,
Blue,
}
if you use MyEnum.Red.ToString() in another assembly, and in between times someone has recompiled your enum to:
enum MyEnum
{
Black,
Red,
Blue,
}
at runtime, you will get "Black".
(NB, I am not especially interested in the ToString here, the problem is that if calling code asks for MyEnum.Red, it's actually referencing MyEnum.Black)
Another example to illustrate the problem with explicit sequential numbering:
enum ECountry
{
Afghanistan = 1,
Barbados = 2,
Chile = 3,
...
Zambia = 243,
}
If Giggleswick suddenly declares itself a republic, what do you do?
No, it's not a good idea.
You can't safely recreate a string from it's hash code. You can loop through the names and compare their hash code with the one that you have, but there is still a small risk of a false positive due to hash code collisions. The risk is rather small, but it's still not a reliable method to use.
Just use the name of the enum value if you need to convert from one version of an enum to another. The ToString method gives you the name, and you can use the Enum.Parse method to get the enum value from the string.
Edit:
Using the string value of course requires that you have one occurance of the enum in each assembly so that it's compiled each time. If you are using an enum in a referenced assembly, then you have to give each value a specific numeric representation, otherwise you don't have anything at all that keeps the value consistent from one version to the next.
You won't have problems with negative values. You might have a problem with duplicate values, since the hashcode for a string isn't guaranteed to be unique.
Though, I'd rather just set the values explicitly to 1, 2, 3 and so.
The hashcode of two different string values is not guaranteed to be unique, so that seems like a dangerous option. What is the problem with setting the values explicitly? Unless the numbers have a meaning in your object model then there would seem to be no reason to leave room for new values - just add new options onto the end of the enum.
I'm not sure if I'm misinterpreting things or not, but..
MyEnum.Red.ToString() should yield "Red" not 0. As such an Enum.Parse should be fine with a correct string evaluation. The problem you're describing would be int Foo = (int)MyEnum.Red; Which would be 0; This could cause the inconsistent results described on after a recompile with new elements.
Based on my understanding of the requirements, enum.ToString is persisted, there should be no issue. The issue is when persisting the value or the enum, not the string representation. Therefore, no hashing should be necessary.
Here's an example (pretend we added black)...
class Program
{
static void Main(string[] args)
{
// this is the approach described
string RedToString = MyEnum.Red.ToString();
// We're pretending that red was the zeroth element when we did (int)MyEnum.Red;
int RedToInt = 0;
MyEnum RedFromString = (MyEnum)Enum.Parse(typeof(MyEnum), RedToString);
MyEnum RedFromInt = (MyEnum)RedToInt;
// this is in theory how RedToInt was persisted
int BlackFromInt = (int)MyEnum.Black;
Console.WriteLine("RedToString: " + RedToString); // Red
Console.WriteLine("RedToInt: " + RedToInt); // 0
Console.WriteLine("RedFromString: " + RedFromString); // Red
Console.WriteLine("RedFromInt: " + RedFromInt); // Black
Console.WriteLine("BlackFromInt: " + BlackFromInt); // 0
Console.Read();
}
enum MyEnum
{
Black,
Red,
Blue,
}
}
HTH,
Ben

Categories