I have a List<byte> that stores the value of a variable byte by byte. I am trying to build up this variable by respect to its original data type.
Example of the result:
List<byte> varBytes = new List<byte>();
varBytes.Add(0x12);
varBytes.Add(0x34);
varBytes.Add(0x56);
varBytes.Add(0x78);
//After the conversion of UInt32:
varReady = 0x78563412;
Here is a snippet of my class that returns the value of the variable.
public static object GetTypedString(List<byte> varBytes, string varType)
{
object varReady;
switch (varType)
{
case "uint16":
UInt16 varReady = BitConverter.ToUInt16(varBytes.ToArray<byte>(), 0);
break;
case "uint32":
UInt32 varReady = BitConverter.ToUInt32(varBytes.ToArray<byte>(), 0);
break;
//repeat case for each data type
}
return varReady ;
}
The problem comes up if my variable is only 2 bytes long and if I want to show that variable as UInt32. The BitConverter.ToUInt32 will throw this exception:
Destination array is not long enough to copy all the items in the collection.
Because the varBytes list only has 2 bytes but BitConverter.ToUInt32 is trying to read 4 bytes. My solution was to add dummy bytes to the end of the list in this case:
.
.
.
case "uint32":
int difference = sizeof(UInt32) - varSize; //we know the variable size already
if(difference > 0)
{
varToDisp.value.AddRange(new byte[difference]);
}
UInt32 varReady = BitConverter.ToUInt32(varBytes.ToArray<byte>(), 0);
break;
.
.
.
This works but didn't seem a good way to me since it will edit the original List and consumes some time. Is there any easier way to achieve this?
You can create the array (not list) with required Length with a help of Linq Concat; I suggest routine redesign as well.
Code:
// Let's implement a generic method: we want, say, uint not object from given list
public static T GetTypedString<T>(List<byte> varBytes) where T: struct {
if (null == varBytes)
throw new ArgumentNullException(nameof(varBytes));
// sizeof alternative
// char is Ascii by default when marshalling; that's why Marshal.SizeOf returns 1
int size = typeof(T) == typeof(char)
? sizeof(char)
: System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
// if data is too short we should pad it; either from left or from right:
// {0, ..., 0} + data or data + {0, ..., 0}
// to choose the way, let's have a look at endiness
byte[] data = (size >= varBytes.Count)
? BitConverter.IsLittleEndian
? varBytes.Concat(new byte[size - varBytes.Count]).ToArray()
: new byte[size - varBytes.Count].Concat(varBytes).ToArray()
: varBytes.ToArray();
// A bit of reflection: let's find out suitable Converter method
var mi = typeof(BitConverter).GetMethod($"To{typeof(T).Name}");
if (null == mi)
throw new InvalidOperationException($"Type {typeof(T).Name} can't be converted");
else
return (T)(mi.Invoke(null, new object[] { data, 0 })); // or data.Length - size
}
Then you can use it as follow:
List<byte> varBytes = new List<byte>();
varBytes.Add(0x12);
varBytes.Add(0x34);
varBytes.Add(0x56);
varBytes.Add(0x78);
int result1 = GetTypedString<int>(varBytes);
long result2 = GetTypedString<long>(varBytes);
Console.WriteLine(result1.ToString("x"));
Console.WriteLine(result2.ToString("x"));
// How fast it is (Linq and Reflection?)
var sw = new System.Diagnostics.Stopwatch();
int n = 10000000;
sw.Start();
for (int i = 0; i < n; ++i) {
// The worst case:
// 1. We should expand the array
// 2. The output is the longest one
long result = GetTypedString<long>(varBytes);
//Trick: Do not let the compiler optimize the loop
if (result < 0)
break;
}
sw.Stop();
Console.WriteLine($"Microseconds per operation: {(sw.Elapsed.TotalSeconds/n*1000000)}");
Outcome:
78563412
78563412
Microseconds per operation: 0.84716933
Edit: If you insist on type name (string varType) instead of generic parameter <T> first of all let's extract a model (type name - type correspondense):
private static Dictionary<string, Type> s_Types =
new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase) {
{ "uint16", typeof(UInt16)},
{ "ushort", typeof(UInt16)}, // <- you can add synonyms if you want
{ "int", typeof(Int32)},
{ "int32", typeof(Int32)},
{ "long", typeof(Int64)},
{ "int64", typeof(Int64)},
//TODO: add all the other names and correspondent types
};
Then you can implement it as
public static object GetTypedString(List<byte> varBytes, string varType) {
if (null == varBytes)
throw new ArgumentNullException(nameof(varBytes));
else if (null == varType)
throw new ArgumentNullException(nameof(varType));
Type type = null;
if (!s_Types.TryGetValue(varType, out type))
throw new ArgumentException(
$"Type name {varType} is not a valid type name.",
nameof(varBytes));
// sizeof alternative
// char is Ascii by default when marshalling; that's why Marshal.SizeOf returns 1
int size = typeof(T) == typeof(char)
? sizeof(char)
: System.Runtime.InteropServices.Marshal.SizeOf(typeof(T));
byte[] data = (size >= varBytes.Count)
? BitConverter.IsLittleEndian
? varBytes.Concat(new byte[size - varBytes.Count]).ToArray()
: new byte[size - varBytes.Count].Concat(varBytes).ToArray()
: varBytes.ToArray();
var mi = typeof(BitConverter).GetMethod($"To{type.Name}");
if (null == mi)
throw new InvalidOperationException(
$"Type {type.Name} (name: {varType}) can't be converted");
else
return mi.Invoke(null, new object[] { data, 0 }); // data.Length - size
}
Demo:
string result1 = (GetTypedString(varBytes, "Int64") as IFormattable).ToString("x8", null);
Rather than using .ToArray you could preallocate your array to the correct size and use .CopyTo.
Example:
var byteArray = new byte[sizeof(UInt32)];
varBytes.CopyTo(byteArray);
UInt32 varReady = BitConverter.ToUInt32(byteArray, 0);
You can check for the length of the array and convert it to smaller types then cast the required one
case "uint32":
{
if (varBytes.Count == 1)
{
varReady = (UInt32)varBytes[0];
}
else if (varBytes.Count >= 2 && varBytes.Count < 4)
{
varReady = (UInt32)BitConverter.ToUInt16(varBytes.ToArray<byte>(), 0);
}
else
{
varReady = BitConverter.ToUInt32(varBytes.ToArray<byte>(), 0);
}
break;
}
Related
I'm new to c# and want to process strings according to the following pattern:
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
/*
- if string contains > 5 characters --> Split
- check, which is the longest array from the split
- use the longest split to be an array 2D
*/
// expected result
var new_data = new List<object[]> {
new object[] { "ABCDE", 80, "TestM", "PQRST" },
new object[] { "FGHIJ", " ", "ain", "UVWXY" },
new object[] { "KLMNO", " ", " ", " " }
}
You will have to constrain your List<object> to a List<string>, since you cannot assure a valid conversion back to the original type, once you split it.
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
List<string> stringData = data.Select(o => o.ToString()).ToList();
const int maxCharacters = 5;
int nrOfEntries = data.Count;
List<string[]> result = new List<string[]>();
while (true)
{
bool finished = true;
string[] newRow = new string[nrOfEntries];
for (int i = 0; i < nrOfEntries; i++)
{
string currentString = stringData[i];
if (string.IsNullOrEmpty(currentString))
{
newRow[i] = " ";
continue;
}
int length = currentString.Length;
int charactersToTake = Math.Min(length, maxCharacters);
int charactersRemaining = length - charactersToTake;
newRow[i] = currentString.Substring(0, charactersToTake);
switch (charactersRemaining)
{
case 0:
stringData[i] = null;
break;
default:
stringData[i] = currentString.Substring(charactersToTake, charactersRemaining);
finished = false;
break;
}
}
result.Add(newRow);
if(finished)
break;
}
You could use List<object[]> result, but that list will only contain strings (and will only be useful as such) since there is no way you can convert back arbitrary objects, as stated before.
I would use Linq to solve the problem. (Be sure you have using System.Linq; at the top of your code file!)
First of all, we define a function to break down an object into several strings with length 5 or less or the object itself, if it is not a string.
object[] BreakDownObject(object o)
=> BreakDownObjectToEnumerable(o).ToArray();
IEnmuerable<object> BreakDownObjectToEnumerable(object o)
{
// If object is string, thant yield return every part
// with 5 characters (or less than 5, if necessary,
// for the last one)
if(o is string s)
{
for(int i = 0; i < s.Length; i += maxStringLength)
{
yield return s.Substring(i, Math.Min(s.Length - i, maxStringLength));
}
}
// object is not a string, don't break it up
else
{
yield return o;
}
}
Wie use Substring in Combination with Math.Min. If length - index is smaller than 5, than we use this instead for the substring.
If we use this function on all items of the list we get an array of arrays of object. This array could be interpreted as "columns", because the first index gives us the columns, and the second index the subsequent broken down strings.
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
object[][] columns = data.Select(BreakDownObject).ToArray();
Now we want to transpose the array, so rows first. We write a function, that takes an index and our array of arrays and returns the row with that index. (Again I use Linq-IEnumerable for easier creation of the array):
object[] GetRowAtIndex(int index, object[][] columns)
=> GetRowAtIndexAsEnumerable(index, columns).ToArray();
IEnumerable<object> GetRowAtIndexAsEnumerable(int index, object[][] columns)
{
foreach(var column in columns)
{
// Each column has different length,
// if index is less than length, we
// return the item at that index
if(index < column.Length)
{
yield return column[index];
}
// If index is greater or equal length
// we return a string with a single space
// instead.
else
{
yield return " ";
}
}
}
This function also fills up missing items in the columns with a one-space string.
Last but not least, we iterate through the rows, until no column has items left:
List<object[]> GetAllRows(object[][] columns)
=> GetAllRowsAsEnumerable(columns);
Enumerable<object[]> GetAllRowsAsEnumerable(object[][] columns)
{
int index = 0;
while(true)
{
// Check if any column has items left
if(!columns.Any(column => index < column.Length))
{
// No column with items left, left the loop!
yield break;
}
// return the row at index
yield return GetRowAtIndex(index, columns);
// Increase index
++index;
}
}
Put it together as one function:
List<object[]> BreakDownData(List<object> data)
{
object[][] columns = data.Select(BreakDownObject).ToArray();
return GetAllRows(columns);
}
After that, your code would be:
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
var new_data = BreakDownData(data);
I'm recieving UDP messages as byte arrays and depending on details in some of the first byte, I need to convert remaining bytes into a multitude of different possible arrangements of other data types.
These data types are primarily 'uint', 'ushort' and just 'byte' but can be in any order, and there can be any number of them. Each of these items will have a named variable.
I've tried using quite a few different options and am getting close, but feel that the methods I've created are not the best they could be. In places I've wanted to use 'sizeof' but don't want to mark the code as 'unsafe'. I've tried to use the 'params' keyword on the input, but this cannot be in conjunction with 'ref'. I've wanted to pass them with a maximum number of T1, T2, etc. using generics, but realised I can't enumerate these. I'm now passing an array of variables in conjunction with the 'ref' keyword, which means creating the array in memory but only the array gets updated with the changed and not the original variables.
byte[] message = new byte[] { }; //some byte array
ushort item1 = default(ushort);
byte item2 = default(byte);
var argumentArray = new object[] { item1, item2 };
ConvertArray(response, ref argumentArray);
private void ConvertArray(byte[] response, ref object[] items)
{
int index = 0;
for (int i = 0; i < items.Length; i++)
{
var item = items[i];
var itemType = item.GetType();
var itemSize = SizeOf(item.GetType());
if (itemSize == 0)
{
continue;
}
else if (itemSize == 1)
{
items[i] = response[index];
}
else
{
var method = typeof(BitConverter).GetMethod($"To{itemType.Name}");
var returned = method.Invoke(null, new object[] { response, index });
items[i] = Convert.ChangeType(returned, itemType);
}
index = index + itemSize;
}
}
private int SizeOf(Type type)
{
switch (type.Name)
{
case nameof(UInt16):
return 2;
case "Byte":
return 1;
default:
return 0;
}
}
So this is partially working in that 'argumentArray' is updating with the values from the 'ConvertArray' method, but I'm sure there is a neater way to do this using Types.
Ideally I wouldn't need to create the 'argumentArray' and just pass the items (e.g. Item1 and Item2) directly as arguments to the method.
I'm trying to write a reflection class (for custom serialization).
I'm stumped on the syntax for iterating through an array.
The following works for non-arrays, but I'm missing the array part, and couldn't puzzle it out from other answers here (some of which do not compile)...
To implement deserialization, write-able references to each object in the target class must be obtained in order to set their values from the previously-serialized data.
Thanks in advance for any tips !
private static void diagPrint(Object val)
{
if (val == null)
return; // whoops
Type t = val.GetType();
string r = "";
if (t.IsArray)
{
Type t_item = t.GetElementType();
r += "Type=" + t_item.Name + "[], value=[";
//
// ??? OK, now what ? How to iterate through the elements of the array ???
// Needs to work for arrays of simple type like Bool or arrays of objects...
//
r += "]";
}
else
{
r += "Type=" + t.Name + ", value=";
r += val.ToString();
}
MessageBox.Show(r);
}
The simplest way would be to just cast the object as IEnumerable, which arrays implement.
IEnumerable items = val as IEnumerable;
foreach (object o in items)
{
r += ((o != null) ? o.ToString() : "") + ", ";
}
To iterate through an array you can use IEnumerable interface
IEnumerable array = val as IEnumerable;
if (array != null)
{
foreach (var element in array)
{
MessageBox.Show(element.ToString());
}
}
Every array implements IEnumerable.
In addition to the other answers, you can also use the GetValue of the Array class. All arrays inherit from Array so if IsArray is true you can cast in to Array
Note though this is really only needed if you want to adress multidimentional arrays. For most cases you can just cast to IEnumerable. You can even just to that for multidimentional arrays if you only want to enumerate all the values
Here's the answer as shipped. The important bits I initially missed are:
You cannot write to elements inside an IEnumerable (foreach),
forget about trying to cast to an array and use the [] operator (just use get/set value),
boxing is required sometimes in places you don't expect
Note this serializer works for FIXED SIZE classes (attribute classes are used to specify string lengths, target for deserialization must have default initializations for all components and no nulls). This is used where C# built-in serializers are too verbose for storing values into a space-limited embedded device's flash. It doesn't support multi-dimensional arrays (exercise for the reader).
Hope someone finds this helpful, thanks for the suggestions,
Best Regards, Dave
private static void navelGaze(string name, int level, ref object val, int storageSize)
{
Debug.Assert(val != null); // can happen with an unitialized string (please init to "")
Type t = val.GetType();
StringBuilder diagString = new StringBuilder(100);
diagString.Append(name); diagString.Append(": ");
if (t.IsArray)
{
Type t_item = t.GetElementType();
Array ar = val as Array;
int len = ar.Length;
diagString.Append("Type="); diagString.Append(t_item.Name);
diagString.Append("["); diagString.Append(len); diagString.Append("]");
if (t_item.BaseType.Name == "ValueType")
{
// array of primitive types like Bool or Int32...
diagString.Append(", value=[");
// Because C# does not permit modification of the iteration object
// in "foreach", nor subscript operations on an array of primitive values,
// use "GetValue/Setvalue" and a regular index iteration.
for(int idx=0; idx<len; idx++) {
object arrayElementBoxObj = ar.GetValue(idx);
Munger(ref arrayElementBoxObj, 0);
diagString.Append(arrayElementBoxObj.ToString());
diagString.Append(", ");
if (currentOperation == SerializerOperation_T.Deserialize)
ar.SetValue(arrayElementBoxObj, idx);
}
diagString.Append("]");
WriteDiagnostic(level, diagString.ToString());
return;
}
else
{
// This is an array of a complex type; recurse for each element in the array...
WriteDiagnostic(level, diagString.ToString());
// The following cast operation is required to subscript 'ar'...
// Note an array of a primitive type like 'int' cannot be cast to an array of objects.
object[] vResObjArray = (object[])ar;
for(int i=0; i<len; i++) {
object boxObj = vResObjArray[i];
navelGaze(name + "[" + i + "]", level + 1, ref boxObj, 0);
if (currentOperation == SerializerOperation_T.Deserialize)
{
// Setting vResObjArray[x] DOES set the original object passed into
// this function, as required for deserialization.
vResObjArray[i] = boxObj;
}
}
return;
}
}
else if (t.Name == "String")
{
// 'String' is actually a class, but normal class handling below blows up...
diagString.Append("Type="); diagString.Append(t.Name);
diagString.Append(", value=");
Munger(ref val, storageSize);
diagString.Append(val.ToString());
}
else if (t.IsClass)
{
// Decompose class and recurse over members
WriteDiagnostic(level, diagString + "Class " + t.Name);
// Note that custom attributes are associated with the class's fields and properties,
// NOT the type of the value of the fields/properties, so this must be checked here
// prior recursion to process the values...
// GetFields does not get the PROPERTIES of the object, that's very annoying...
FieldInfo[] fi = val.GetType().GetFields();
foreach (FieldInfo f in fi)
{
if (f.IsStatic) continue; // ignore class constants
// Skip this if the FIELD is marked [HSB_GUI_SerializeSuppress]
HSB_GUI_SerializeSuppressAttribute[] GUI_field_suppressSerializationAtt =
(HSB_GUI_SerializeSuppressAttribute[])
f.GetCustomAttributes(typeof(HSB_GUI_SerializeSuppressAttribute), true);
if (GUI_field_suppressSerializationAtt.Length > 0)
continue; // this field is marked with "suppress serialization to GUI save are"
// Get optional size specifier (required for strings)
int nextLevelStorageSize = 0;
HSB_GUI_SerializableAttribute[] HSB_GUI_SerializableAtt =
(HSB_GUI_SerializableAttribute[])
f.GetCustomAttributes(typeof(HSB_GUI_SerializableAttribute), true);
if (HSB_GUI_SerializableAtt.Length > 0)
nextLevelStorageSize = HSB_GUI_SerializableAtt[0].StorageLength;
// box, and gaze into this field...
object boxObj = f.GetValue(val);
// could replace null with default object constructed here
navelGaze(f.Name, level + 1, ref boxObj, nextLevelStorageSize);
if (currentOperation == SerializerOperation_T.Deserialize)
f.SetValue(val, boxObj);
}
// Now iterate over the PROPERTIES
foreach (PropertyInfo prop in /*typeof(T)*/ val.GetType().GetProperties())
{
// Skip this if the PROPERTY is marked [HSB_GUI_SerializeSuppress]
HSB_GUI_SerializeSuppressAttribute[] GUI_prop_suppressSerializationAtt =
(HSB_GUI_SerializeSuppressAttribute[])
prop.GetCustomAttributes(typeof(HSB_GUI_SerializeSuppressAttribute), true);
if (GUI_prop_suppressSerializationAtt.Length > 0)
continue; // this property is marked with "suppress serialization to GUI save are"
// not relevant; does not occur: if (!type.IsSerializable) continue;
// Get optional size specifier (required for strings)
int nextLevelStorageSize = 0;
HSB_GUI_SerializableAttribute[] HSB_GUI_SerializableAtt =
(HSB_GUI_SerializableAttribute[])
prop.GetCustomAttributes(typeof(HSB_GUI_SerializableAttribute), true);
if (HSB_GUI_SerializableAtt.Length > 0)
nextLevelStorageSize = HSB_GUI_SerializableAtt[0].StorageLength;
// box, and gaze into this field...
object boxObj = prop.GetValue(val, null);
// could replace null with default object constructed here
navelGaze("Property " + prop.Name, level + 1, ref boxObj, nextLevelStorageSize);
if (currentOperation == SerializerOperation_T.Deserialize)
prop.SetValue(val, boxObj, null);
}
return;
}
else if (t.IsEnum)
{
Munger(ref val, 0);
diagString.Append("Enum ("); diagString.Append(t.Name); diagString.Append("), value=");
diagString.Append(val.ToString()); diagString.Append(", raw value="); diagString.Append((int)val);
}
else
{
Munger(ref val, 0);
diagString.Append("Type="); diagString.Append(t.Name);
diagString.Append(", value="); diagString.Append(val.ToString());
}
WriteDiagnostic(level,diagString.ToString());
}
public static HSB_Settings_Class DeserializeResult(byte[] datastream)
{
Debug.Assert(datastream.Length == 0x0600);
idx = 0;
buf = datastream;
currentOperation = SerializerOperation_T.Deserialize;
HSB_Settings_Class new_HSB_settings = new HSB_Settings_Class();
object boxObj = new_HSB_settings;
navelGaze("DeserializeResult", 0, ref boxObj, 0);
new_HSB_settings = (HSB_Settings_Class)boxObj;
Console.WriteLine("==== Deserialization used a total of " + idx.ToString() + " bytes (out of 1536 available) ====");
return new_HSB_settings;
}
public static byte[] SerializeHSBsettings(ref HSB_Settings_Class hsbset)
{
idx = 0;
currentOperation = SerializerOperation_T.Serialize;
object boxObj = hsbset;
navelGaze("SerializeHSB", 0, ref boxObj, 0);
Console.WriteLine("==== Serialization used a total of "+idx.ToString() + " bytes (out of 1536 available) ====");
return buf;
}
Fair warning, this may be a tad esoteric and tricky.
Given a MemberRef (more explanation below) extracted from a CIL stream, how do you figure out what field, if any, it points to (and get a FieldInfo for it)?
Here's what I've figured out so far
According to the ECMA 335 standard, a MemberRef is a metadata token that is basically a lookup in a table that could point to either a field metadata token or a method metadata token. Any metadata token beginning 0x0A is a MemberRef.
I hadn't encountered one of these before, but they don't seem that uncommon. I was able to get one generated by using the following anonymous type in a method:
new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
}
When I grab the method body via reflection (get the PropertyInfo, get the GetMethod, get the MethodBody, then finally get the IL) A's get method is:
[2, 123, 79, 0, 0, 10, 42]
Which converts to:
ldarg.0
ldfld 0x0A00004F
ret
If I reflect in and get the backing field (relying on name similarity to choose <A>i__Field, nothing algorithmic) I see that the MetadataToken is 0x04000056.
Note that the tokens generated may vary between compilations.
A token starting 0x04 is a field:
Most of the time (for all non-anonymous objects in my limited testing, in fact) the IL contains a field metadata token. This is easy to turn into a FieldInfo via Module.ResolveField(int), figuring out what to do with a MemberRef is tripping me up.
Cycling through the other ResolveXXX methods on Module, the only one that can do anything with a MemberRef is ResolveSignature. When run on the above MemberRef, it returns an array of [6, 19, 0]. I don't really know what to make of that.
The code I'm working on is unfinished, but public. The error can be seen by running this test, causing an exception to be thrown when a field lookup fails on this line. Note that the test itself is unfinished, it's not expected to succeed yet but it shouldn't die there either.
Anybody have any idea what to make of that signature, or some other way to get to a field's metadata token (and thus its FieldInfo) from the MemberRef?
Here's a LINQPad program script that reproduces the problem. It's pretty big, there's a lot of boilerplate.
void Main()
{
Init();
var obj =
new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
};
var usage = PropertyFieldUsage(obj.GetType());
usage.Dump();
}
private static Dictionary<int, System.Reflection.Emit.OpCode> OneByteOps;
private static Dictionary<int, System.Reflection.Emit.OpCode> TwoByteOps;
public static Dictionary<PropertyInfo, List<FieldInfo>> PropertyFieldUsage(Type t)
{
var ret = new Dictionary<PropertyInfo, List<FieldInfo>>();
var props = t.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic).Where(p => p.GetMethod != null);
var module = t.Module;
foreach (var prop in props)
{
var getMtd = prop.GetMethod;
var mtdBody = getMtd.GetMethodBody();
var il = mtdBody.GetILAsByteArray();
var fieldHandles = _GetFieldHandles(il);
var fieldInfos =
fieldHandles
.Select(
f => module.ResolveField(f)
).ToList();
ret[prop] = fieldInfos;
}
return ret;
}
// Define other methods and classes here
private static List<int> _GetFieldHandles(byte[] cil)
{
var ret = new List<int>();
int i = 0;
while (i < cil.Length)
{
int? fieldHandle;
System.Reflection.Emit.OpCode ignored;
var startsAt = i;
i += _ReadOp(cil, i, out fieldHandle, out ignored);
if (fieldHandle.HasValue)
{
ret.Add(fieldHandle.Value);
}
}
return ret;
}
private static int _ReadOp(byte[] cil, int ix, out int? fieldHandle, out System.Reflection.Emit.OpCode opcode)
{
const byte ContinueOpcode = 0xFE;
int advance = 0;
byte first = cil[ix];
if (first == ContinueOpcode)
{
var next = cil[ix + 1];
opcode = TwoByteOps[next];
advance += 2;
}
else
{
opcode = OneByteOps[first];
advance++;
}
fieldHandle = _ReadFieldOperands(opcode, cil, ix, ix + advance, ref advance);
return advance;
}
private static int? _ReadFieldOperands(System.Reflection.Emit.OpCode op, byte[] cil, int instrStart, int operandStart, ref int advance)
{
Func<int, int> readInt = (at) => cil[at] | (cil[at + 1] << 8) | (cil[at + 2] << 16) | (cil[at + 3] << 24);
switch (op.OperandType)
{
case System.Reflection.Emit.OperandType.InlineBrTarget:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineSwitch:
advance += 4;
var len = readInt(operandStart);
var offset1 = instrStart + len * 4;
for (var i = 0; i < len; i++)
{
advance += 4;
}
return null;
case System.Reflection.Emit.OperandType.ShortInlineBrTarget:
advance += 1;
return null;
case System.Reflection.Emit.OperandType.InlineField:
advance += 4;
var field = readInt(operandStart);
return field;
case System.Reflection.Emit.OperandType.InlineTok:
case System.Reflection.Emit.OperandType.InlineType:
case System.Reflection.Emit.OperandType.InlineMethod:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineI:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineI8:
advance += 8;
return null;
case System.Reflection.Emit.OperandType.InlineNone:
return null;
case System.Reflection.Emit.OperandType.InlineR:
advance += 8;
return null;
case System.Reflection.Emit.OperandType.InlineSig:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineString:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.InlineVar:
advance += 2;
return null;
case System.Reflection.Emit.OperandType.ShortInlineI:
advance += 1;
return null;
case System.Reflection.Emit.OperandType.ShortInlineR:
advance += 4;
return null;
case System.Reflection.Emit.OperandType.ShortInlineVar:
advance += 1;
return null;
default: throw new Exception("Unexpected operand type [" + op.OperandType + "]");
}
}
static void Init()
{
var oneByte = new List<System.Reflection.Emit.OpCode>();
var twoByte = new List<System.Reflection.Emit.OpCode>();
foreach (var field in typeof(System.Reflection.Emit.OpCodes).GetFields(BindingFlags.Public | BindingFlags.Static))
{
var op = (System.Reflection.Emit.OpCode)field.GetValue(null);
if (op.Size == 1)
{
oneByte.Add(op);
continue;
}
if (op.Size == 2)
{
twoByte.Add(op);
continue;
}
throw new Exception("Unexpected op size for " + op);
}
OneByteOps = oneByte.ToDictionary(d => (int)d.Value, d => d);
TwoByteOps = twoByte.ToDictionary(d => (int)(d.Value & 0xFF), d => d);
}
The trick here is that it is a generic type (the second parameter to ResolveField), and we know that the getter is not a generic method (the final parameter to ResolveField), so you need to use ResolveField like so:
var obj = new
{
A = new DateTime(1234, 5, 6, 7, 8, 9, DateTimeKind.Utc),
B = (DateTime?)null
};
Parse(obj, "A");
Parse(obj, "B");
static void Parse(object obj, string property)
{
var blob = obj.GetType().GetProperty(property).GetGetMethod()
.GetMethodBody().GetILAsByteArray();
// hard-code that we know the token is at offset 2
int token = BitConverter.ToInt32(blob, 2);
var field = obj.GetType().Module.ResolveField(token,
obj.GetType().GetGenericArguments(), null);
Console.WriteLine(field.Name);
Console.WriteLine(field.MetadataToken);
}
In the more general case, where you don't know as much about the type (i.e. that it might be a non-generic type) and the method (although strictly speaking, property accessors are never generic in their own right, but this shows broad usage):
static MemberInfo ResolveMember(this MethodInfo method, int metadataToken)
{
Type type = method.DeclaringType;
Type[] typeArgs = null, methodArgs = null;
if (type.IsGenericType || type.IsGenericTypeDefinition)
typeArgs = type.GetGenericArguments();
if (method.IsGenericMethod || method.IsGenericMethodDefinition)
methodArgs = method.GetGenericArguments();
return type.Module.ResolveMember(metadataToken, typeArgs, methodArgs);
}
I am trying to implement the IComparable interface in my custom object so that List.Sort() can sort them alphabetically.
My object has a field called _name which is a string type, and I want it to sort based on that. Here is the method I implemented:
public int CompareTo(object obj)
{
//Int reference table:
//1 or greater means the current instance occurs after obj
//0 means both elements occur in the same position
//-1 or less means the current instance occurs before obj
if (obj == null)
return 1;
Upgrade otherUpgrade = obj as Upgrade;
if (otherUpgrade != null)
return _name.CompareTo(otherUpgrade.Name);
else
throw new ArgumentException("Passed object is not an Upgrade.");
}
Not sure if I did something wrong or if it's just the way the string CompareTo works, but basically my List was sorted like this:
Test Upgrade
Test Upgrade 10
Test Upgrade 11
Test Upgrade 12
Test Upgrade 13
Test Upgrade 14
Test Upgrade 15
Test Upgrade 2
Test Upgrade 3
Test Upgrade 4
Test Upgrade 5
I want them to be sorted like this:
Test Upgrade
Test Upgrade 2
Test Upgrade 3
...etc
You want "natural order" -- the collation that a human who is familiar with the conventions of English would choose -- as opposed to what you've got, which is "lexicographic" collation: assign every letter a strict ordering, and then sort by each letter in turn.
Jeff has a good article on some of the ins and outs here, with links to different algorithms that try to solve the problem:
http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
and Raymond discussed how Windows dealt with it here:
http://technet.microsoft.com/en-us/magazine/hh475812.aspx
Basically the problem is: natural order collation requires solving an artificial intelligence problem; you're trying to emulate what a human would do, and that can be surprisingly tricky. Good luck!
Strings are sorted in lexicographic order. You'll have to either format all your numbers to have the same length (eg: Test Upgrade 02) or parse the number in your comparer and incorporate it in your comparison logic.
The reason this happens is that you are doing string comparison, which has no explicit knowledge of numbers. It orders each string by the respective character codes of each character.
To get the effect you want will require a bit more work. See this question: Sort on a string that may contain a number
AlphaNumeric Sorting
public class AlphanumComparatorFast : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
Usage :
using System;
using System.Collections;
class Program
{
static void Main()
{
string[] highways = new string[]
{
"100F",
"50F",
"SR100",
"SR9"
};
//
// We want to sort a string array called highways in an
// alphanumeric way. Call the static Array.Sort method.
//
Array.Sort(highways, new AlphanumComparatorFast());
//
// Display the results
//
foreach (string h in highways)
{
Console.WriteLine(h);
}
}
}
Output
50F 100F SR9 SR100
Thanks for all the replies guys. I did my own method and it seems to work fine. It doesn't work for all cases but it works for my scenario. Here is the code for anyone who is interested:
/// <summary>
/// Compares the upgrade to another upgrade
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
//Int reference table:
//1 or greater means the current instance occurs after obj
//0 means both elements occur in the same position
//-1 or less means the current instance occurs before obj
if (obj == null)
return 1;
Upgrade otherUpgrade = obj as Upgrade;
if (otherUpgrade != null)
{
//Split strings into arrays
string[] splitStringOne = _name.Split(new char[] { ' ' });
string[] splitStringTwo = otherUpgrade.Name.Split(new char[] { ' ' });
//Will hold checks to see which comparer will be used
bool sameWords = false, sameLength = false, bothInt = false;
//Will hold the last part of the string if it is an int
int intOne = 0, intTwo = 0;
//Check if they have the same length
sameLength = (splitStringOne.Length == splitStringTwo.Length);
if (sameLength)
{
//Check to see if they both end in an int
bothInt = (int.TryParse(splitStringOne[splitStringOne.Length - 1], out intOne) && int.TryParse(splitStringTwo[splitStringTwo.Length - 1], out intTwo));
if (bothInt)
{
//Check to see if the previous parts of the string are equal
for (int i = 0; i < splitStringOne.Length - 2; i++)
{
sameWords = (splitStringOne[i].ToLower().Equals(splitStringTwo[i].ToLower()));
if (!sameWords)
break;
}
}
}
//If all criteria is met, use the customk comparer
if (sameWords && sameLength && bothInt)
{
if (intOne < intTwo)
return -1;
else if (intOne > intTwo)
return 1;
else //Both equal
return 0;
}
//Else use the default string comparer
else
return _name.CompareTo(otherUpgrade.Name);
}
else
throw new ArgumentException("Passed object is not an Upgrade.");
}
Will work for strings spaced out using a " " character, like so:
Test data:
Hello 11
Hello 2
Hello 13
Result
Hello 2
Hello 11
Hello 13
Will not work for data such as Hello11 and Hello2 since it cannot split them. Not case sensitive.