[Propousal] ? mark generic parameter type / Reflection for all #2932
Replies: 53 comments
-
object oldValue = ...;
if (oldValue is INotifyCollectionChanged collection)
{
collection.CollectionChanged += dynamicGrid.collectionChangedCallback;
} |
Beta Was this translation helpful? Give feedback.
-
Again you !! Do not be such straight forward even if it is possible for interface, imagine the situation where is no interface or interface is generic ? What to do in this case ? |
Beta Was this translation helpful? Give feedback.
-
I probably comment on most proposals on this repo. Call it boredom. 😁
I'd say it depends on the use case. But faking some kind of generic subclass and emitting reflection-based code seems like a very inefficient and fragile way to accomplish any task. This seems relatively niche because it could only apply to generic classes where you don't know the generic type argument and you need to call a method that does not require you to know the generic type argument. Let's complicate the use case with the following: public struct ObservableCollectionChanged<T> {
public T OldValue { get; set; }
public T NewValue { get; set; }
public int Index { get; set; }
}
public class ObservableCollection<T> {
public event EventHandler<ObservableCollectionChanged<T>> CollectionChanged;
} Even if the compiler would let you reference the type as |
Beta Was this translation helpful? Give feedback.
-
I will note that I do like Java wildcards and variance, which do work with the Object val = ...;
if (val instanceof List) {
List<?> list = (List<?>) val;
int size = list.size();
} This is easier in Java because generics don't exist at runtime and |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Seems like you are right ... Using types that depend on generic parameter type is useless ... But lets think a little bit ... What if we allow use methods, events, properties that do not depend on generic parameter type in user context ? I will show an example: public struct ObservableCollectionChanged<T>
{
public T OldValue { get; set; }
public T NewValue { get; set; }
public int Index { get; set; }
}
public class ObservableCollection<T>
{
public event EventHandler<ObservableCollectionChanged<T>> CollectionChanged;
public int Index { get; set; } = 0;
public T Item {
get
{
return _list[Index];
}
set
{
_list[Index] = value;
}
}
private List<T> _list;
}
interface ISocketData
{
void send();
}
class Program
{
void SomeCalculation<T>(T item)
{
Console.WriteLine(item);
}
void SendOverNetwork<T>(T item) where T: ISocketData
{
item.send();
}
static void Main(string[] args)
{
object cl = new ObservableCollection<string>();
if (cl is ObservableCollection<$> collection) {
int index = collection.Index; // Ok
int item = collection.Item; // Compilation error, cannot take Item of unknown type and convert it to int
$ item = collection.Item; // Good, because it is taken as generic parameter type. $ here is any generic parameter
SomeCalculation(item); // Also good because generic item type do not have constraints as well as SomeCalculation method
SendOverNetwork(item); // Compilation error, item do not satisfy ISocketData constraint
}
}
} As you have mentioned in my example I have changed ? -> $ as generic parameter type |
Beta Was this translation helpful? Give feedback.
-
This is now looking very much like something that would be solved with Shapes (#164) or Roles (#1711). |
Beta Was this translation helpful? Give feedback.
-
How exactly do you propose this work? Not even Java permits this, and it has fairly loose wildcard bounds given that generics don't actually exist at runtime.
There's still a lot of reflection, you're just having the compiler emit it. You can't represent any of this using proper IL. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour In this example I can do very little with item, but if this item has constraint I can apply to it some operations: $ item = collection.Item;
item.send(); // Could work if Item has type T that has constraint ISocketData And $ mean meant in my example type inference, actually we can use existed keyword var as well: var item = collection.Item;
item.send(); // Could work if Item has type T that has constraint ISocketData And the final example would be: interface ISocketData
{
void send();
}
class SocketData : ISocketData {
public void send()
{
// some implementation
}
}
static void Main(string[] args)
{
object cl = new ObservableCollection<SocketData>();
if (cl is ObservableCollection<?> collection) {
int index = collection.Index; // Ok
int item = collection.Item; // Compilation error, cannot take Item of unknown type and convert it to int
var item = collection.Item; // Good, because it is taken as generic parameter type. Compiler will deduce item type as ISocketData
// or you can do like this
ISocketData item = collection.Item; // Good, because it is taken as generic parameter type. Compiler will deduce item type as ISocketData
SomeCalculation(item); // Also good because generic item type do not have constraints as well as SomeCalculation method
SendOverNetwork(item); // Compilation error, item do not satisfy ISocketData constraint
}
}
But it is better from user point of view ... =) But what is wrong if compiler will emit all this reflection stuff ? |
Beta Was this translation helpful? Give feedback.
-
Reflection code is very slow - so there's benefit in requiring developers to be explicit when using it, so they know that they're paying that performance cost. Hiding reflection behind a language feature like this makes it extremely likely that some will use it without realizing the cost. |
Beta Was this translation helpful? Give feedback.
-
@theunrepentantgeek Also garbage collection could be expensive !! But who care ? If developer does not face with performance issue than he wants such performance ... |
Beta Was this translation helpful? Give feedback.
-
Nullable reference types is a special case where the LDM decided it was worth introducing a feature flag because the expected benefit is huge. They've been very clear in the past that they do not want to create different dialects of the C# language because it shards the community, creating different incompatible factions. One factor that's often overlooked is that every feature flag doubles the number of dialects, it's not incremental. If you had just five worthwhile features governed by feature flags, that's 32 dialects of the language ... |
Beta Was this translation helpful? Give feedback.
-
Okay, I agree, then please add this feature using reflection under the hood !! What is the problem ?
Okay, okay, I see what's the problem ... You think that reflection should be obvious, because of performance, but what about async await, they also adding pressure on code size and on run-time and what ? Adding this feature by compiler simplified a lot of boilerplate code, on the other hand issue with performance you can leave to performance analysis tool or by adding another flags that shows warning on performance trade-offs by generated code ... Anyway, because it is generated code you can even optimize it a little bit, when user could wrote such code that would not be possible to optimize !! |
Beta Was this translation helpful? Give feedback.
-
THere are other features that are being planned which may subsume this entirely. So it wouldn't make sense to do this feature if it's just going to be immediately obsoleted by other mechanisms. |
Beta Was this translation helpful? Give feedback.
-
What are the other mechanisms ? |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi @HaloFour @theunrepentantgeek public static async ValueTask<object?> InvokeValueAsync(this MethodInfo @this, object obj, params object[] parameters)
{
var task = (ValueTask<?>)@this.Invoke(obj, parameters);
await task!.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty!.GetValue(task);
} And it is not possible ... ( One solution is: public static async ValueTask<object?> InvokeValueAsync(this MethodInfo @this, object obj, params object[] parameters)
{
dynamic task = @this.Invoke(obj, parameters);
await task!.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty!.GetValue(task);
} But it is not possible for iOS platform with keyword dynamic ... ( I was lucky because for this particular struct I can do something like this: public static async ValueTask<object?> InvokeValueAsync(this MethodInfo @this, object obj, params object[] parameters)
{
var task = @this.Invoke(obj, parameters);
var newTask = (Task?)task!.InvokeMethod("AsTask");
await newTask!.ConfigureAwait(false);
var resultProperty = newTask.GetType().GetProperty("Result");
return resultProperty!.GetValue(newTask);
} But I was lucky because imagine how may cases where no simple conversion from generic struct to something to call method !! |
Beta Was this translation helpful? Give feedback.
-
The choice here is that you will have to call to the actual instantiations you get. There is no other way given the limitations your have stated. I don't know what you're going for here :-). If your runtime is going to have these limitations, there's nothing we can do about that. Any solution we'd make would have the same limitations. I.e. we'd have to call into reflection. So it would still not work on your platform. If dynamic introspection is blocked we cannot dynamically introspect your generic types. You can't have it both ways. |
Beta Was this translation helpful? Give feedback.
-
If you can use reflection, what is the problem? |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi How to use reflection for such example: struct SomeStruct<TItem>
{
public bool IsAvailable => Item != null;
public TItem? Item { get; set; }
}
object someStruct = new SomeStruct<int>();
...
// Some code that use reflection and I as consumer do not know generic type int ???
var particularStruct = (SomeStruct<?How to cast?>) someStruct; Do not forget that |
Beta Was this translation helpful? Give feedback.
-
You use reflection to negotiate the surface of the type and call its members directly. It has nothing to do with casting. var type = someStruct.GetType();
var property = type.GetProperty("IsAvailable");
var isAvailable = (bool) property.GetValue(someStruct); |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Uh, it is bad example from my side :) |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi @HaloFour With such feature would be possible to call extension methods on type with |
Beta Was this translation helpful? Give feedback.
-
@MadsTorgersen @CyrusNajmabadi @HaloFour I cannot find a good example for this feature that could not be done with reflection ... But it does not mean that this feature is meaningless, because if such feature would exist then it is like reflection api for humans where you can to work with types even that you do not have all information for this type and use this only needed properties of this type Lets consider pros and cons of this feature: Pros:
Cons:
|
Beta Was this translation helpful? Give feedback.
-
Java generics work very differently because they don't actually exist at runtime at all. That lets you play fast and loose, but also makes the language features much more complicated. Wildcards and bounds are one of the most complicated aspects of the Java language and can be very easy to get wrong (and infuriating to fix without resorting to unchecked conversions). Adding wildcards and bounds to the .NET runtime and to every .NET language would be a massive undertaking. Considering that there are multiple ways to workaround this issue it seems to me that there'd be very little reason to do so. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Again, I understand how generics in Java works, as for me in C# generics works much better because at run-time I can get real type of collection But this feature will make code more readable: var particularStruct = (SomeStruct<?>) someStruct;
var isAvailable = particularStruct.IsAvailable; instead of: var type = someStruct.GetType();
var property = type.GetProperty("IsAvailable");
var isAvailable = (bool) property.GetValue(someStruct); Code with
During casting to As you can see this feature is only compiler technology, runtime generics are the same
What happens if user will try to access var particularStruct = (SomeStruct<?>) someStruct;
var isAvailable = particularStruct.IsAvailable; // No problems
var item = particularStruct.Item; // Error, cannot deduce type of Item
var item = (object) particularStruct.Item; // No problems
particularStruct.Item.CallMethod(); // Error, call method on unknown type As you can see this feature simplifies work with generic collection with unknown generic parameter |
Beta Was this translation helpful? Give feedback.
-
No, it couldn't, not without massive changes to how generics work in the runtime. IMO making reflection code slightly easier to read in order to work around poorly designed APIs seems far from a justifiable reason to invest so much into gutting and reworking generics. |
Beta Was this translation helpful? Give feedback.
-
@HaloFour Why do you need to rework whole generic runtime behavior ? I consider this feature only as one extra instruction to IL code, that's all ... As for me it seems like feasible, but seems like you have some issues with internal structure of generic, how they saved in runtime, am I right ? |
Beta Was this translation helpful? Give feedback.
-
You can't really state this about ' an extra instruction'. i.e. i coudl have a single isntruction which was 'solve the traveling salesperson problem' :) And that wouldn't make it easy or simple to provide. You have to actually explain and justify the complexity that wuold have to go into adding support for that instruction. In this case, it seems substantial (but i'm open to an argument that it would not be). |
Beta Was this translation helpful? Give feedback.
-
An instruction that casts an instance to a type that doesn't exist and allows invoking members with partial signatures would most certainly not easy to implement. If you think it is you can feel free to go to the runtime repo and tell them how they would do it. |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi Any feature is adding some complexity, but this feature as for me deserve to work with complexity, because it will bring "Reflection for all" and make other people wirting frameworks, libraries, improve learning C# easier and also improve quality of reflection code |
Beta Was this translation helpful? Give feedback.
-
It's a matter of ROI. This feature involves a lot of complexity, especially given how much work would be required in the runtime to make it possible even before considering what language syntax might look like. If the benefit is limited to making it slightly easier to write code that you can write today using reflection or dynamic then it's certainly not worth that effort. This is a niche situation that is easily solved using language and runtime features that have been there since the beginning. Massive amounts of work for very little reward in very niche scenarios, it's not worth it. |
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.
-
When I have object type variable and want to get concrete type now you need to do some thing like this:
and I want to subscribe on CollectionChanged I need today to do something like this:
Proposal is to simplify it a little bit by adding Generic Parameter Type inference:
it also could be simplified like this:
Also imagine more complex example:
Beta Was this translation helpful? Give feedback.
All reactions