Make Value Tuple property names resolvable at runtime #1906
Replies: 71 comments 2 replies
-
I just want to point out that I am just an application developer. I might not have provided the best explanation in the original post. And I understand that there might exist technical limitations that make the issue impossible to implement. But I did not find an existing issue for this and I think it deserves an issue of it's own in this repository. Even if it ends with a big "no". I believe the serialization limitation is something that everyone using Value Tuples regularly will someday have to realize or deal with. At least it deserves a mention in the Microsoft Docs page for tuples. |
Beta Was this translation helpful? Give feedback.
-
Do you understand how tuples are implemented using the public class C
{
public (int x, int y) Position { get; set; }
} compiles to: public class C
{
[TupleElementNames(new[] { "x", "y" })]
public ValueTuple<int, int> Position { get; set; }
} So in this case, a serializer could get the names. Do you have a suggestion on how to solve this without breaking backwards compatibility?
In that case, you should use the Feedback button on that article, so that the documentation team can learn about this. |
Beta Was this translation helpful? Give feedback.
-
Thank you very much for the answer with good points!
No, I guess not. I am afraid I am not knowledgeable enough in the language design low-level technicalities. As I said, this issue is from the perspective of an application developer. I am sorry if I am overstepping boundaries by posting here without expertise. But to clarify for myself, if I understand correctly then the issue is that:
gets converted to:
and then it is currently impossible to convey any additional information in the Taking a shot in the dark I guess it could be possible to always wrap the ValueTuple in another struct which would always have the attribute about property names. So that's a suggestion and you can shoot me down why it's bad or doesn't work. |
Beta Was this translation helpful? Give feedback.
-
The limitations for serialization were known at the time that tuples were designed. Tuples aren't intended to be a nominal data type used in such a manner. They are meant to be a positional grouping of loosely related data elements, like a parameter list. The names are there only to help the developer in referencing the elements, but they're largely ephemeral.
How would you propose this work? Could you provide pseudocode or the like to demonstrate it? I don't see how it would be possible to do this without causing many breaking changes. |
Beta Was this translation helpful? Give feedback.
-
I'm strictly spit-balling here but perhaps there is opportunity for the compiler to support an overload that accepts an optional // given
void Serialize<T>(T tuple, TupleElementNames names = null) where T : struct, ITuple {
// do stuff here
}
// the following
var person = (name: "Bill Gates", age: 62);
Serialize(person);
// would compile into
ValueTuple<string, int> person = ValueTuple.Create("Bill Gates", 62);
Serialize(person, new TupleElementNames(new string[] { "name", "age" }); Such a method would be pretty limited, but it would at least give you the ability reflect the element names of a local tuple. |
Beta Was this translation helpful? Give feedback.
-
Tuples are designed for ephemeral groupings of related information - for when a method needs multiple return values, or to avoid having the same trio of variables with a common prefix throughout a class, for two examples. If you need something like a tuple that has permanent names, use a struct (or maybe a record, if #39 happens). Throw in deconstruction and an implicit cast from a similar tuple, and you've got syntax compatibility with tuples, along with all of the persistent naming and other extensiblity you might want. |
Beta Was this translation helpful? Give feedback.
-
I think any implementation of this would greatly increase technical debt by squeezing tuples into a domain they weren't originally designed for. |
Beta Was this translation helpful? Give feedback.
-
My use case for this is that i need to be able to do some logging, easily serializing all input parameters and storing them. I can do this in a single line of code, it is quite uniform in the code, and i will not need an arbitrary amount of structures to do that. I am doing this now, but i do get those nasty ItemN names. Now that i am thinking about it, and from a more functional perspective, it would be even nicer if i had a keyword (something like value in full properties), that would contain the method parameters in a tuple. I mean, from a math perspective, it is already a tuple. |
Beta Was this translation helpful? Give feedback.
-
Folks normally use anonymous types for this kind of dynamic situation. See: Entity Framework, Dapper, Castle Windsor, etc. |
Beta Was this translation helpful? Give feedback.
-
Item1, Item2, ..., Item7 are fields, not a properties. Also, depending how it gets implemented it could be a breaking change as Reflection wouldn't be able to detect fields with custom names, so how would those who works with reflection may get/set the value of a field if we don't know how are they called at runtime? For example public class Foo
{
public ValueTuple<int,int> SomeProperty { get; set;}
}
void Bar()
{
var foo = new Foo();
foo.SomeProperty = (1, 2);
var tupleField = foo.GetType().GetProperty("SomeProperty").PropertyType.GetField("Item1");
// what do you think this will return if you allow it to have a different name at runtime?
} |
Beta Was this translation helpful? Give feedback.
-
I hope we can find a proper solution to this, as serializing to json is something I need to do quite often. public struct UShortVector2
{
public ushort X { get; set; }
public ushort Y { get; set; }
public UShortVector2(ushort x = 0, ushort y = 0)
{ this.X = x; this.Y = y; }
// Dynamic Type conversion from ushort touples
public static implicit operator UShortVector2((ushort x, ushort y) touple)
=> new UShortVector2(touple.x, touple.y);
public static implicit operator (ushort, ushort) (UShortVector2 v)
=> (v.X, v.Y);
} With the two implicit conversion operators I can then use (ushort, ushort) a = new UShortVector2(3, 4);
UShortVector2 b = (5, 3); Serializing a UShortVector will output "X" and "Y" properly, that way my json files look readable while I can use the easy touple syntax for coding. But this is only be a temporary workaround, I'd like to see the touples be improved upon in that regard, I wanted to use them to prevent having additional classes/structs. |
Beta Was this translation helpful? Give feedback.
-
This "tuple was designed for bla bla bla" talk sounds like you are trying to post-justify mistakes caused by technical constraints with supposed "design decisions". First of all, I don't even agree that "was designed for whatnot" is really that good of an argument, since there are plenty of things that were designed for one thing in the language, but have evolved to be used for other purposes, and I'm surprised to see this argument pop up here so often. I've seen this argument being used in the Secondly, what is ephemeral anyway? Why is returning a tuple from a method and sending it to another considered ephemeral, but serializing one in order to send it somewhere else is not? If I serialize a tuple, send it to a API, the API deserialize the tuple, deconstructs it, do some calculation and then answers me with something, why isn't this ephemeral? Is there now some definition for ephemeral objects in language design which conveniently fits your "OK usage scenarios" for Thirdly, records, at least in the current proposed form, are not enough. Sometimes you simply don't want to declare a class for your thing. If records (in the current form) were enough, then we could just as well be declaring classes all over, instead of using Tuples. It's not like writing Fourthly, come on guys, this is just ugly. I feel like we've done the Java generics type erasure thing we all like to bash out, only we did it with names instead of types (which could actually be part of the type were Tuples nominally typed). It just doesn't feel right. I think it's a prime example of the opposite of the Pit of Success. It should just work™. I feel like any other day a language will come up with tuples just like C#'s, but they'll do it right and support querying items' name information during runtime and we'll look at that like we look on Java vs C# generics. |
Beta Was this translation helpful? Give feedback.
-
Compiler adds some attributes to the CustomAttributes field of the ValueTuple type information. Having custom attribute(s) with names of the fields seems quite logical solution to me. It would not break any existing code and would allow reflection of the true names. That in turn would enable proper serialization which in turn would break already existing serialization code relying on ItemN field name format. The other solution could be even simpler. Proper parameter names could be added to the generated ValueTuple constructor. Right now even constructor parameters are Item1, Item2. |
Beta Was this translation helpful? Give feedback.
-
... generated constructor?? |
Beta Was this translation helpful? Give feedback.
-
The C# compiler could conceivably generate concrete types for named Tuples, and used the when tuples are declared: (string Name, int Age) person = ("Jane", 21); Would generate a compiler only class something like public struct <NamedTuple>person__2: IComparable, IComparable<ValueTuple<string,int>>, IEquatable<ValueTuple<string,int>>, IStructuralComparable, IStructuralEquatable, ITuple
{
(string Name, int Age) _backingTupple;
public string Name { get => _backingTupple.Name; set => _backingTupple.Name = value;}
public int Age { get => _backingTupple.Age; set => _backingTupple.Age = value; }
public <NamedTuple>person__2(string name, int age)
{
this._backingTupple.Name = name;
this._backingTupple.Age = age;
}
public static ref ValueTuple<string, int> GetTuple(ref <NamedTuple>person__2 person)
{
return ref person._backingTupple;
}
public void Deconstruct(out string name, out int age)
{
name = _backingTupple.Name;
age = _backingTupple.Age;
}
public int CompareTo(object other, IComparer comparer)
{
return ((IStructuralComparable)_backingTupple).CompareTo(other, comparer);
}
public int CompareTo((string, int) other)
{
return ((IComparable<(string, int)>)_backingTupple).CompareTo(other);
}
public int CompareTo(object obj)
{
return ((IComparable)_backingTupple).CompareTo(obj);
}
public bool Equals(object other, IEqualityComparer comparer)
{
return ((IStructuralEquatable)_backingTupple).Equals(other, comparer);
}
public bool Equals((string, int) other)
{
return ((IEquatable<(string, int)>)_backingTupple).Equals(other);
}
public int GetHashCode(IEqualityComparer comparer)
{
return ((IStructuralEquatable)_backingTupple).GetHashCode(comparer);
}
public int Length => ((ITuple)_backingTupple).Length;
public object this[int index] => ((ITuple)_backingTupple)[index];
} Basically a wrapper around the value tuple, which is a generated struct with members. Since this option was considered in the initial design and rejected because of type duplication and problems around structural types polution. (In which assemblies do you generate tuple wrappers? in all?) maybe a pragmatic solution would be to annotate the tuple members where such a class would be generated. [SerializableTuple]
(string Name, int Age) person = ("Jane", 21); (Yes this is very similar to Records in some respects, except for the immutability) |
Beta Was this translation helpful? Give feedback.
-
I already wrote that feature :) Invoke it, and you get a rename session allowing you to pick an appropriate name for the new struct: |
Beta Was this translation helpful? Give feedback.
-
I also contributed features to support converting anonymous types to tuples, as well as converting anonymous types to classes. All these features work for VB and C# :) |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi nice! Is that already bundled in VS or is it an extension? |
Beta Was this translation helpful? Give feedback.
-
it's bundled in VS. It was added back in July: dotnet/roslyn#28257. I hope it will be helpful to you :) |
Beta Was this translation helpful? Give feedback.
-
Can we get a Anonymous() or Instantiate() extension method which converts a named valuetuple to an anonymous type ? I note this will cover most of the use cases without getting into custom serializers . eg to Serialize just go JsonConvert( tuple.Instantiate()) . I don't think the perf diff will mater much compared to the IO cost. The whole reason for using these is you dont want all the class type just for going through a layer. Note in this case you dont care about the ordering ..
|
Beta Was this translation helpful? Give feedback.
-
What's the difference between that, and a:
? |
Beta Was this translation helpful? Give feedback.
-
Note that anonymous types are generated by the compiler, at compile-time. Any method would have to work at runtime. A method also wouldn't know the names of the tuple members - all of the problems which apply to passing a tuple to Ok also apply to Instantiate. |
Beta Was this translation helpful? Give feedback.
-
@canton7 I thought so too initially, but since this is the language repo and not corefx, you could special-case the method and have, for example, Another approach could be for any method, like |
Beta Was this translation helpful? Give feedback.
-
Sure, but you still need to define the type of the anonymous type at runtime. It's not just a matter of instantiating the anonymous type, you also need to define it. Somewhere along the line, the code Sure you could do it at runtime with Reflection.Emit, but that has all sorts of problems on ios, AOT, etc. It seems to me that the only way to do this would be a new language construct (rather than a new extension method, as was proposed), where the compiler would take a call to e.g. I could see the value in an attribute which collects the member names for a tuple (at least that would let people write methods which accept a tuple, and also get the member names), but that gets a little complex if a method accepts multiple tuples, and it means that all of these serialization APIs need a new overload which accepts a ValueTuple. |
Beta Was this translation helpful? Give feedback.
-
Named tuples are very useful for strongly typing and naming the results from queries where the type definition might be one-off and inline for the given query but the type information is still needed for the implementation. I think the term "ephemeral" has misled the design to suggest that the type signature of a named tuple is disposable. A better term would be "adhoc". An adhoc type might be "of the moment" but no matter how a programmer defines a thing, the type information needs to flow through the rest of the language otherwise it causes confusing dead-ends like we are experiencing here. IMHO "no erasure of type information" needs to be a core design principle for a strongly typed language supporting reflection. With this principle we can then ensure the user level requirements like "all values can be serialised/deserialised" are achievable. |
Beta Was this translation helpful? Give feedback.
-
Is it going to be implemented or not? This discussion exists over 2 years and there is still no any concrete judgment. Let's decide something and give us a hope or kill it finally. |
Beta Was this translation helpful? Give feedback.
-
feature request: if you have a tuple with named parameters, a visual studio helper to promote them to a struct record? |
Beta Was this translation helpful? Give feedback.
-
An interesting point made by https://blog.marcgravell.com/2021/05/is-era-of-reflection-heavy-c-libraries.html is that source generators do have access to all tuple element names, and source generation has a number of significant advantages over using reflection which are outlined in that blog post. |
Beta Was this translation helpful? Give feedback.
-
I would say this is a language deficiency, because the compiler is quite happy to accept lambdas that refer to named tuple elements - but at runtime, code that resolves the member from that lambda will return the name of the base tuple member. The following example outputs However I cannot think of a reasonable fix for this scenario, that would not break existing code that depends upon the current behaviour... using System.Linq.Expressions;
using System.Reflection;
namespace ConsoleApp32
{
internal class Program
{
static void Main(string[] args)
{
var typed = new Typed<(int Foo, string Bar)>();
var member = typed.GetMember(t => t.Foo);
// outputs "Item1", not "Foo"
Console.WriteLine(member!.Name);
}
}
public class Typed<T>
{
public MemberInfo? GetMember<TProperty>(Expression<Func<T, TProperty>> expression)
=> expression.GetMember();
}
public static class Extensions
{
public static MemberInfo? GetMember<T, TProperty>(this Expression<Func<T, TProperty>> expression)
{
if (RemoveUnary(expression.Body) is not MemberExpression memberExp)
{
return null;
}
var currentExpr = memberExp.Expression;
while (true)
{
currentExpr = RemoveUnary(currentExpr);
if (currentExpr != null && currentExpr.NodeType == ExpressionType.MemberAccess)
{
currentExpr = ((MemberExpression)currentExpr).Expression;
}
else
{
break;
}
}
if (currentExpr == null || currentExpr.NodeType != ExpressionType.Parameter)
{
return null;
}
return memberExp.Member;
}
private static Expression? RemoveUnary(Expression? toUnwrap)
{
return toUnwrap is UnaryExpression unaryExpression
? unaryExpression.Operand : toUnwrap;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
My 2 cents are, don't use tuples, just spend an extra 20 seconds (if that) and write a I know tuples are part of C# and there is no going back on them, but I still think they were a mistake to add and should be avoided in development. Again, just my opinion. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
C# tuple types
I hate that this cool useful C# language feature is incomplete. It is bogged down by the fact that there exists no library which is able to serialize Value Tuples with the correct property names. (They will still be "Item1", "Item2" etc.) This is because there is no information at runtime about the property names. Until serialization is perfectly possible I can not use Value Tuples seriously as 1st class citizens of the language. It is far too common of a requirement to be able to serialize an object and have the output in human-readable format.
Quick examples where the current serialization doesn't cut it:
A quote from Microsoft Docs:
Basically, the problem of no semantic information still exists...
Additional links:
C# 7 ValueTuple types and their limitations
Newtonsoft's Json.NET GitHub issue
C# Tuples. More about element names.
"I can't get parameter names from valuetuple via reflection in c# 7.0"
Beta Was this translation helpful? Give feedback.
All reactions