C# 7 and Tuple casting through event aggregator - c#

In C# 7 tuples became powerful:
var tuple = (speed: 45, color: "red");
Coupled with an event aggregator:
EvtAgreg.Publish("SomeInfo", (
speed: 45,
color: "red",
));
That would be very powerful to make decoupled code. However, from what I've gathered it seems impossible to recast that.
EvtAgreg.Subscribe("SomeInfo", this.GetType().Name, (obj) => {
var data = ((int, string)) obj;
}
However, this is not flexible and was wondering if there might be another way to cast on runtime similar to JS.
EvtAgreg.Subscribe("SomeInfo", this.GetType().Name, (obj) => {
var data = (?) obj;
data.speed ... or data["speed"]
}
Obviously that isn't possible, however, would it be through a class holding the tuple and its format (Type) ?
Edit: Trying to bring clarity about the question.
It is not about casting, but how to make the casting simple.
The point is, if you have a complex dataset of say 20 values that can be of different types, the casting becomes something like
var x = ((string, int, int, int, float, string, string, int, int, int, string, int, int, int, float, string, string, int, int, int)) obj;
Which becomes impossible to maintain.
Here is a way to achieve something similar with Hashtable:
Publisher:
MessageManager.Publish("Hardware_NewIRInfos",
new Hashtable() {
{ "position", i },
{ "status", (int)Load.IRStatus(i) },
{ "serial", (int)Load.IRSerial(i) },
{ "id", (int)Load.IRID(i) },
{ "version", Load.IRVersion(i) },
{ "cal", (int)Load.IRCalDate(i) },
{ "emissivity", Load.IREmissivity(i) },
{ "transmissivity",Load.IRTransmissivity(i) },
{ "gain", Load.IRGain(i) },
{ "offset", Load.IROffset(i) }
});
Subscriber:
MessageManager.Subscribe("Hardware_NewIRInfos", this.GetType().Name, (obj) =>
{
Hashtable data = (Hashtable)obj;
IRSensorParameters[pos ].Channel = pos;
IRSensorParameters[pos ].Status = (int) data["status"];
IRSensorParameters[pos ].ID = (int)data["id"]; ;
IRSensorParameters[pos ].Serial = (int) data["serial"];
IRSensorParameters[pos ].Version = (string)data["version"];
IRSensorParameters[pos ].CalibrationDate = (int) data["cal"];
IRSensorParameters[pos ].Emissivity = (float)data["emissivity"];
IRSensorParameters[pos ].Transmissivity = (float)data["transmissivity"];
IRSensorParameters[pos ].Gain = (float)data["gain"];
IRSensorParameters[pos ].Offset = (float)data["offset"];
});
In here you still have static casting, but it is much easier to maintain.
Is it possible to have a similar, better way, using C# 7 Tuples?

You are missing one set of brackets:
var data = ((int, string)) obj;
Think of it like this:
to cast you write (MyType)obj so casting requires set of brackets,
to declare tuple you wirite (int, string) so it requires its own set of brackets,
it cannot be the same set of brackets so you get double.
as long as your inner types match you can cast to whatever named properties you want:
var a = ((int speed, string color))(3,"red");
var c = ((int someInt, string someString))a;
var color = c.someString;
Also you can always use generic names Item1 Item2 they are not hinted by intellisense but are valid. There is no (easy) way to convert it to array but you have constant there either way so this should be fine.
One more nice thing is tuple expansion you can just unload it to variables in one line:
var tuple =(speed: 4, color: "red");
var (number, text) = tuple;
So yes tuples become hard to wrap you head around fast. And I would use them only for private in class communication between methods just because it does not require formal type declaration. Your use of tuple with 20 items is just wrong. Do not do that. In my opinion 4 items in tuple is too much and warrant dedicated type creation. If you really want such very fragile setup you can use your code exactly as you have written Hashtable is valid c# class. I would just create dedicated type.
So the bottom line is no you cannot have this using tuple just create dedicated class or use already defined type - whatever is in IRSensorParameters[pos]. To avoid dependency you can send string that contains your data eg. in json format.

Related

Create Tuple Using List of datatype of string in c#

I need to create Tuple from list of datatype in string, but did not get any solution.
Here is example of what I want to do.
string[] dataType = {"int", "float", "datetime"};
//I want to create list like this but dynamically datatype come from db.
//List<Tuple<int, string, string>> list = new List<Tuple<int, string, string>>();
List<Tuple<dataType[0],dataType[1], dataType[2]>> list = new List<Tuple<dataType[0],dataType[1], dataType[2]>>();
//Here datatype value is in string so I want to convert string to actual datatype.
Or if any alternative solution for this please guide me.
This is to extend my comments under question, and show you what I meant there.
It is not hard to create a list of Tuple dynamically from list of types in string format. For example, use reflection:
private Type InferType(string typeName)
{
switch (typeName.ToLowerInvariant())
{
case "int":
return typeof(int);
case "float":
return typeof(float);
default:
return Type.GetType($"System.{typeName}", true, true);
}
}
private object CreateListOfTupleFromTypes(string[] types)
{
var elementTypes = types.Select(InferType).ToArray();
// Get Tuple<,,>
var tupleDef = typeof(Tuple)
.GetMethods(BindingFlags.Static | BindingFlags.Public)
.First(mi => mi.Name == "Create"
&& mi.GetParameters().Count() == elementTypes.Length)
.ReturnType
.GetGenericTypeDefinition();
// Get Tuple<int, float, DateTime>
var tupleType = tupleDef.MakeGenericType(elementTypes);
// Get List<Tuple<int, float, DateTime>>
var listType = typeof(List<>).MakeGenericType(tupleType);
// Create list of tuple.
var list = Activator.CreateInstance(listType);
return list;
}
The problem is because the list is created using types only known at runtime, in your code, you can never use the list as strong typed list. i.e.List<Tuple<int, float, DateTime>>.
Of course, you could make life easier when the list is used in your code by using ITuple:
var list = new List<ITuple>();
list.Add(new Tuple<int, float, DateTime>(...);
int value1 = (int)list[0][0];
float value1 = (float)list[0][1];
DateTime value1 = (DateTime)list[0][2];
However, if you do that, then there is no point to use Tuple. You only need List<object[]>.
So, this comes back to my question, what is the list of tuple for in your code?
You could use dynamic here for your types. For example, when you read the three values from your database you could assign them without worrying about the types like so:
dynamic a = 10;
dynamic b = "Some String";
dynamic c = new DateTime(2020,4,9);
var test = new System.Tuple<dynamic,dynamic,dynamic>(a, b, c);
It would depend on what you want to do with the values later on.
To create a list of those tuples use:
var list = new List<System.Tuple<dynamic,dynamic,dynamic>>();
You can get the type of the variable, for instance a with
a.GetType().ToString();
I am not sure if you can set a variable type based on a string value, though (the second part of your question).

cast list of anonymous type to list of object

I am trying to create a anonymous list which can hold any data type with intellisense support but without creating a class.
So I found below solution to use anonymous type.
var list = new[]
{
new { Number = 10, Name = "Smith" },
new { Number = 10, Name = "John" }
}.ToList();
foreach (var item in list)
{
Console.WriteLine(item.Name);
}
But what if I want a method which returns above anonymous type.
public List<object> GetData()
{
var list = new[]
{
new { Number = 10, Name = "Smith" },
new { Number = 10, Name = "John" }
}.ToList();
return list;
}
Compile Time Error:
Cannot implicitly convert type
'System.Collections.Generic.List<>' to 'System.Collections.Generic.List
Is it possible to cast list of anonymous type to list of object with intellisense support ?
Update:
The reason we don't want to create a type , because we just want to do some data manipulation using the anonymous type and populate some other objects that's all.
Here are the ways possible:
Return a List<object>, which means you have no intellisense on the receiving end
Return a List<dynamic>, which means you have no intellisense on the receiving end, but perhaps easier to access members you know are there than through reflection
Return a List<T> but then you will have to provide an example of how T is supposed to look and this won't be any more safe at runtime than dynamic is
Return a tuple of the new type that came with C# 7
Point 1 and 2 can be easily solved by just ensuring the list is of that type:
...
}.ToList<object>();
Point 3 is a hack but I'll post it below.
Point 4 can be solved with this syntax:
public List<(int Number, string Name)> GetData()
{
var list = new[]
{
(Number: 10, Name: "Smith"),
(Number: 10, Name: "John")
}.ToList();
return list;
}
This will give you intellisense for a while but the naming of the properties is a hack by the compiler and if you start passing these values around they will easily fall back to .Item1 and .Item2.
To cast an object to a specific type you can use a hack which only works in the same assembly that the anonymous object was created in, and that is that multiple anonymous types used around in your code, which has the same properties, in the same order, with the same property types, all end up being the same anonymous type.
You can thus cast an object to a specific anonymous type with this hackish code:
public T AnonymousCast<T>(object value, T example) => (T)value;
public IEnumerable<T> AnonymousCastAll<T>(IEnumerable<object> collection, T example) => collection.OfType<T>();
You would use it in your case like this:
var d = AnonymousCast(GetData()[0], new { Number = 0, Name = "" });
This is no more safe than using dynamic as there is no guarantee the object returned from GetData actually is of that anonymous type.
In short, use a named type.

Cannot implicitly convert type System.Collections.Generic.Dictionary<System.Tuple<int,int,string>, AnonymousType#1>

I have the following dictionary in a method:
var nmDict = xelem.Descendants(plantNS + "Month").ToDictionary(
k => new Tuple<int, int, string>(int.Parse(k.Ancestors(plantNS + "Year").First().Attribute("Year").Value), Int32.Parse(k.Attribute("Month1").Value), k.Ancestors(plantNS + "Report").First().Attribute("Location").Value.ToString()),
v => {
var detail = v.Descendants(plantNS + "Details").First();
return
new
{
BaseHours = detail.Attribute("BaseHours").Value,
OvertimeHours = detail.Attribute("OvertimeHours").Value
};
});
I need to return nmDict. The problem is that I cannot figure out how to label my method signature. I have tried the following:
protected IDictionary<XElement, XElement> OvertimereportData(HarvestTargetTimeRangeUTC ranges)
The above gives me this error:
Cannot implicitly convert type System.Collections.Generic.Dictionary<System.Tuple<int,int,string>,AnonymousType#1>' to 'System.Collections.Generic.IDictionary<System.Xml.Linq.XElement,System.Xml.Linq.XElement>'. An explicit conversion exists (are you missing a cast?)
protected IDictionary<Tuple, XElement> OvertimereportData(HarvestTargetTimeRangeUTC ranges)
gives me this error:
'System.Tuple': static types cannot be used as type arguments
I do not know what to do.
The short answer: You can't return anonymous types from a function.
The long answer: Your dictionary's value type is anonymous {BaseHours, OvertimeHours} which cannot be returned from a function or passed as an argument (except as an object, but that does nobody any good unless you go through the hassle of reflecting into it). Either define a class/struct with BaseHours and OvertimeHours in it, or use a tuple. The former is probably slightly better because you can keep the names BaseHours and OvertimeHours; with a tuple you just get Value1 and Value2.
If you are using C# 4.0 than you can return the anonymous via dynamic type. So your method signature would look like this
protected IDictionary<Tuple<int,int,string>, dynamic> OvertimereportData(HarvestTargetTimeRangeUTC ranges)
And through the dynamic object you can find the properties at run time.
Hope this will help you.
When you call the ToDictionary method, the resulting dictionary's type has little to do with the type of elements in your source sequence. It's defined entirely by the data types returned by the key and value expressions you supply to the call. For example, if you were to call:
xelem.Descendants(plantNS + "Month").ToDictionary(
k => int.Parse(k.Attribute("Year").Value),
v => k.Attribute("Year).Value
);
You would get an IDictionary<int, string> because that's what your two expressions returned. To return that from a method, you just need to construct the correct type, based on your expressions.
Your first one is easy:
k => new Tuple<int, int, string>(...)
The second one, though, is going to be a problem. The values in your dictionary are of an anonymous type: you return a new { } without specifying a concrete type name for that value. In general, that is going to make it impossible for you to use that dictionary as a return value or parameter. (It can be done, using some very strange-looking generic techniques, but I wouldn't recommend it.)
The first thing you'll need to do, then, is make a concrete type to hold your values, e.g.
public class HoursContainer
{
public string BaseHours { get; set; }
public string OvertimeHouse { get; set; }
}
and change your Linq query appropriately:
var detail = v.Descendants(plantNS + "Details").First();
return new HoursContainer
{
BaseHours = detail.Attribute("BaseHours").Value,
OvertimeHours = detail.Attribute("OvertimeHours").Value
};
Once you've done this, your dictionary will have a concrete type based on the types of things you specified when you created it:
IDictionary<Tuple<int, int, string>, HoursContainer>
(Note: You could also just use another Tuple<int, int> or whatever here, if you wanted, but the resulting generic type would get unwieldy very fast.)

Better writing of a static Dictionary

I am keeping a static dictionary to map a simple integer stored in a database to an enum value.
static Dictionary<long, EModelType> AttributeIdTypeToEModelType =
new Dictionary<long, EModelType>()
{
{1, EModelType.StatStr},
{6, EModelType.HistStr},
{7, EModelType.HistVal}
};
The great advantage is that I use it to directly get my enum value as the data arrive from the database.
typ = AttributeIdTypeToEModelType[i];
The usage of this system is neat, but it doesn't look clean to me to have a static dictionary just for this.
I had no chance trying to find a cleaner way to use enumerators and overriding their values.
Any advice?
You don't need a Dictionary to cast an int to the appropriate Enum type:
var type = (EModelType)yourInt;
or using Enum.ToObject:
var type = Enum.ToObject(typeof(EModelType) , yourInt);
and you can check that it exists with Enum.IsDefined:
if (! Enum.IsDefined(typeof(EModelType), yourInt)) throw new ArgumentException("Illegal type");
If the value in the database is a string, then use:
EnumType x = (EnumType)Enum.Parse(typeof(EnumType), dr[0].ToString());
If the value in the database is a long, then use:
EnumType x = (EnumType)dr[0];
If you need the long value of an enum, use:
long x = (long)EnumType.SomeEnum
You should define your enum with an int backing type, like this:
public enum EModelType : int
{
StatStr = 1,
HistStr = 6,
HistVal = 7
}
Then, like Tim and Chris suggested, just cast or parse the integer from the database to your enum type.

Get an enumerated field from a string

Bit of a strange one this. Please forgive the semi-pseudo code below. I have a list of enumerated values. Let's say for instance, like so:
public enum Types
{
foo = 1,
bar = 2,
baz = 3
}
Which would become, respectfully, in the code:
Types.foo
Types.bar
Types.baz
Now I have a drop down list that contains the following List Items:
var li1 = new ListItem() { Key = "foo" Value = "Actual Representation of Foo" }
var li2 = new ListItem() { Key = "bar" Value = "Actual Representation of Bar" }
var li3 = new ListItem() { Key = "baz" Value = "Actual Representation of Baz" }
for the sake of completeness:
dropDownListId.Items.Add(li1); dropDownListId.Items.Add(li2); dropDownListId.Items.Add(li3);
Hope that everyone is still with me. What I want to do is to on the Autopostback is take the string "foo" and convert that to Types.foo - without using a switch (as the enumerated values are generated from a database and may change).
I hope that makes sense? Any idea where to even start?
Sure:
Types t;
if(Enum.TryParse(yourString, out t)) // yourString is "foo", for example
{
// use t
}
else
{
// yourString does not contain a valid Types value
}
There's also an overload that takes a boolean that allows you to specify case insensitiveness:
http://msdn.microsoft.com/en-us/library/dd991317.aspx
Enum.TryParse is new in .NET 4. If you're stuck on a previous version, you'll have to use the non-typesafe Enum.Parse method (which throws an exception in case of conversion failure, instead of returning false), like so:
try
{
Types t = (Types)Enum.Parse(typeof(Types), yourString);
// use t
}
catch(ArgumentException)
{
// yourString does not contain a valid Types value
}
Enum.Parse also has an overload for case insensitiveness.
So, you want: Enum.Parse(typeof(Types), postbackValue)
or did I miss something?
If I understood correctly, you can do:
Types fooEnum = Enum.Parse(typeof(Types), "foo");
See: http://msdn.microsoft.com/en-us/library/essfb559.aspx

Categories