Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Language support for KeyValuePair<TKey, TValue> struct and tuple mappings #6624

Closed
Opiumtm opened this issue Jun 10, 2016 · 37 comments
Closed
Milestone

Comments

@Opiumtm
Copy link

Opiumtm commented Jun 10, 2016

KeyValuePair is very often used in C# code in many scenarios.
As there is (k, v) tuple syntax support, I suggest same support for KeyValuePair widely used struct.
dotnet/roslyn#11530

As KeyValuePair and Tuple is a common pattern and sometimes there is custom Tuples or Keyed structs implemented, I suggest generalized built-in support for such tuple patterns and generalized support for keyed classes/structs at language level.

For such purposes I suggest new overloadable operators.

For tuple mappings (enable tuple-syntax initializing and tuple assignment)

public static (,) operator Class1(int field1, double field2)
{
    return new Class1() { Field1 = field1, Field2 = field2 }; 
}

and if class is generic:

public static (,) operator Class2<T1, T2>(T1 field1, T2 field2)
{
    return new Class2() { Field1 = field1, Field2 = field2 }; 
}

This will allow such things:

public Class1 SomeFunc()
{
    // assignment
    Class1 c = (2, 5.0);

    // return value
    return (1, 1.0);
}

So, KeyValuePair<TKey, TValue> should also support tuple operator and instead of new KeyValuePair<string, string>("key", "value") you can just write ("key", "value") if you're returning KeyValuePair from function, assign it to variable or use it as parameter for method.

And for object keys support.

// usage: extract key from object if it support keying.
var key = obj.!;

// define "key" operator
public static int operator .!(Class1 obj)
{
    return obj.Key;
}

// "keyed" constraint in generics

public interface IInterface1<T> where T: with key
{
}

Support for "value" operator

// usage: extract "value" from object
var value = obj.*;

// define "value" operator

public static string operator .*(Class1 obj)
{
    return obj.Value;
}

// if .* operator is not overloaded .* operator should return object itself.
// so default implementation for .* operator should be this, just returning object itself.

// default implementation of .* operator
public static Class2 operator .*(Class2 obj)
{
    return obj;
}

Extract types of keys and values at compile-time

Type keyType = typeof(Class1.!);  // get type of key
Type valueType = typeof(Class1.*);  // get type of value

Use types of keys and values in generics

public interface IKeyedCollection<T, T.!> : IDictionary<T.!, T> where T : with key
{
    void Add(T value);
    void Remove(T.! key);
}

public interface IKeyedDictionary<T, T.!, T.*> : IDictionary<T.!, T.*> where T : with key
{
    void Add(T value);
    void Remove(T.! key);
    T.* GetValue(T.! key);
}

T.! meaning type of key and T.* meaning type of value

And implicit (or explicit) operator conversion from keyed objects for existing APIs compatibility

// built-in implicit conversion for KeyValuePair from any keyed object
public static implicit<T> operator KeyValuePair<T.!, T.*>(T obj)
    where T: with key
{
    return new KeyValuePair<T.!, T*>(obj.!, obj.*);
}

Also added <T> for implicit<T> operator, so implicit conversions can accept generic source object argument

@Opiumtm Opiumtm changed the title Language support for KeyValuePair<TKey, TValue> struct Language support for KeyValuePair<TKey, TValue> struct and tuple-styled initializers Jun 10, 2016
@Opiumtm Opiumtm changed the title Language support for KeyValuePair<TKey, TValue> struct and tuple-styled initializers Language support for KeyValuePair<TKey, TValue> struct and tuple mappings Jun 10, 2016
@HaloFour
Copy link
Contributor

Similar functionality is explicitly mentioned in the meeting notes here: dotnet/roslyn#11205

However they are looking at much more general purpose syntax which works with existing types rather than defining new forms of operators and the like. Your proposal adds a lot of new language concepts for a rather narrow feature and I don't think it's worth it.

@Opiumtm
Copy link
Author

Opiumtm commented Jun 10, 2016

@HaloFour this narrow feature is a starting point. I suggested general approach completely independent from Tuple and KeyValuePair types. "Tuple mapping" is a similar concept to tuple-like constructors, but it generalize approach and merge it with tuples as it allow not only construction but assignment of existing tuples to variable of any type which support tuple mappings.

As for keyed types it's not so "narrow" feature. Key/Value pattern is used massively and currently is tied to KeyValuePair struct. One obvious field of use for keyed types is a SQL-mapped ORM objects as any ORM entity have primary key and used with key in almost any data processing algorithms.

Operator overloading is a natural way to handle such features.

@HaloFour
Copy link
Contributor

Why would you need new general purpose tuple operators when you could just use the existing implicit conversion operators?

public static implicit operator Class2<T1, T2>((T1 field1, T2 field2) tuple)
{
    return new Class2<T1, T2>() { Field1 = tuple.field1, Field2 = tuple.field2 }; 
}

The KVP-specific elements of this proposal involve adding some very non-C# syntax to C#. I'm not sure how you propose that those generic members would work given the nature of generics in the CLR.

@Opiumtm
Copy link
Author

Opiumtm commented Jun 10, 2016

@HaloFour because (T1, T2) tuple in this case is a concrete type mapped to ValueTuple<T1, T2>
"Tuple mapping" operator is not an implicit conversion only, it's a generalized mapping from (value1, value2) syntax. If you're just assign "tuple" value to variable, you will have same functionality as "tuple constructor", if you're assign existing tuple object to variable it will work as implicit conversion. It's essentially a one-way mapping from tuple to named class.

@Opiumtm
Copy link
Author

Opiumtm commented Jun 10, 2016

@HaloFour

The KVP-specific elements of this proposal involve adding some very non-C# syntax to C#. I'm not sure how you propose that those generic members would work given the nature of generics in the CLR.

I suggest approach similar to using with IDisposable pattern. So, for CLR level compatibility you should define IKeyValuePair<TKey, TValue> interface and implicitly implement this interface if you define keyed objects. On CLR-level with key constraint should translate to check for IKeyValuePair<TKey, TValue> interface.

@HaloFour
Copy link
Contributor

HaloFour commented Jun 10, 2016

@Opiumtm

The mapping to an interface constraint isn't enough, you can't dot into a generic type parameter member to ascertain another generic type parameter. IKeyedDictionary<T> : IDictionary<T.!, T.*> is not possible with the CLR, the generic arity of IKeyedDictionary<> would have to be 3. The key and value types must be their own arguments.

public IKeyedDictionary<T, TK, TV> where T : IKeyValuePair<TK, TV> { }

It wouldn't be a good idea to automatically expand the arity as IKeyedDictionary<> and IKeyedDictionary<,,> are distinct types to the CLR and establish their public contract.

@Opiumtm
Copy link
Author

Opiumtm commented Jun 10, 2016

@HaloFour to avoid implicit interface arity expansion it will be better to define IKeyedDictionary<T> as IKeyedDictionary<T, T.!, T.*>. So, interface will have same arity and you can explicitly set order of key and value type arguments in it.

Updated issue thread starting post.

@Opiumtm
Copy link
Author

Opiumtm commented Jun 10, 2016

@HaloFour So, to sum up, on CLR level IKeyedDictionary<T, T.!, T.*> would translate to

public interface IKeyedDictionary<T, TKey, TValue> where T : IKeyValuePair<TKey, TValue>
{
}

If you do not define T.! or T.* type arguments it should be translated to object.

IKeyValuePair<TKey, TValue> should be defined as

public interface IKeyValuePair<out TKey, out TValue>
{
    TKey GetKey();
    TValue GetValue();
}

So you can assign IKeyValuePair<int, string> to unspecified IKeyValuePair<object, object> variable or method parameter.

So, IKeyedDictionary<T> where T : with key (you have omitted T.! and T.* arguments) will translate to

public interface IKeyedDictionary<T> where T : IKeyValuePair<object, object>
{
}

and if you implement other interface

public interface IKeyedDictionary<T> : IDictionary<T.!, T.*> where T : with key
{
}

would translate to

public interface IKeyedDictionary<T> : IDictionary<object, object> 
    where T : IKeyValuePair<object, object>
{
}

If you don't define T.! or T.* type arguments it mean you have no interest in its types and it would default to object and you can not use T.! or T.* types in your interface members, you can only use T type argument as you have not defined it in type argument list.

And another example:

public interface IKeyedCollection<T, T.!> : IDictionary<T.!, T> where T : with key
{
    void Add(T value);
    void Remove(T.! key);
}

will be translated to

public interface IKeyedCollection<T, TKey> : IDictionary<TKey, T> where T : IKeyValuePair<TKey, object>
{
    void Add(T value);
    void Remove(TKey key);
}

@HaloFour
Copy link
Contributor

@Opiumtm So that would render those syntax changes unnecessary. You're not talking about a situation that occurs frequently enough to justify having to invent completely alien syntax just to save maybe a dozen keystrokes.

@Opiumtm
Copy link
Author

Opiumtm commented Jun 10, 2016

@HaloFour I'm talking about very common and especially painful to use in real code pattern. Key/Value pairs are used in many places including modern Windows Runtime APIs. Keyed objects is used everywhere if object have unique ID.

It would add generalized syntax to such keyed objects and key/value pairs.

@jnm2
Copy link
Contributor

jnm2 commented Jun 10, 2016

Strongly dislike IKeyedDictionary. Every dictionary is by definition a keyed dictionary.
Also, other than reading .Key and .Value, I touch KeyValuePairs only three or four times in all code I've ever written. I agree, they can be a pain to write about, but that's so rare. If we're going to do something I'd much rather have a wider syntax that targets more types.

@Opiumtm Opiumtm closed this as completed Jun 10, 2016
@Opiumtm Opiumtm reopened this Jun 10, 2016
@jcouv jcouv self-assigned this Aug 9, 2016
@jcouv jcouv removed their assignment Apr 3, 2017
@dotnet dotnet locked and limited conversation to collaborators Oct 28, 2022
@CyrusNajmabadi CyrusNajmabadi converted this issue into discussion #6625 Oct 28, 2022
This issue is being transferred. Timeline may not be complete until it finishes.

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants