Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c# 9.0 records - reflection and generic constraints

Two questions regarding the new records feature :

  1. How do I recognize a record using reflection ? looking [here][1] maybe there is a way to detect the EqualityContract but I am not sure if that is the way to go ?

  2. Is it possible to have a generic constraint that a generic type is a record ? that is if it is possible to indicate that type parameter T must be a record class using a constraint ?

like image 548
kofifus Avatar asked Jul 26 '20 07:07

kofifus


4 Answers

  1. How do I recognize a record using reflection ?

If you try record classes in sharplab.io you'll see that record classes are usual classes that implement IEquatable<T> interface and contain additional members that are used to compare and clone instances of the record class. There is no special attributes that indicate that the class is a record class.

So I guess that there is no way to determine if a class is a record class using reflection.

looking here maybe there is a way to detect the EqualityContract but I am not sure if that is the way to go ?

It is possible to determine using reflection if a class has such property, but this is not a 100% guarantee that the class with such property is a record class.


  1. Is it possible to have a generic constraint that a generic type is a record ? that is if it is possible to indicate that type parameter T must be a record class using a constraint ?

It is not possible.

  1. Records proposal page does not contain any information about specifying that a generic type parameter T must be a record class.
  2. If you read discussion under this comment at Champion records page you'll learn that there is no way to specify something like where T : record in C# 9. Moreover there are plans to eliminate any meaningful semantic difference between a record and a class in C# 10. So that records' features like with will be available for classes too. Adding record constraint will make this goal not achievable.
like image 112
Iliar Turdushev Avatar answered Nov 01 '22 04:11

Iliar Turdushev


As a 'hack', all records have a synthesized method <Clone>$ which you can look for. Since you can't write a method with that name in C#, a class with a <Clone>$ member is guaranteed to be a record as of C# 9.

However there is no guarantee that this will continue to be the case. For example it's possible that in C# 10.0 some records won't have a <Clone>$ member, or that some non-records will.

public static bool IsRecord(Type type) => type.GetMethod("<Clone>$") != null;
like image 43
Yair Halberstadt Avatar answered Nov 01 '22 06:11

Yair Halberstadt


As mentioned by everyone else, it's not possible to write

private void MyFunc<T>(T t) where T : record {...}

You can however create a record which you then have every record type you create inherit from. This will pretty much achieve what you are asking, although, I don't know how I feel about it...

public abstract record RecordMarker;
public record MyRecord : RecordMarker;
public void MyFunc<T>(T t) where T : RecordMarker
{
}

With this you can only pass record types in since classes cannot inherit from records.

MyFunc(new MyRecord()); // Works
MyFunc(new MyClass());  // Compiler Error
like image 11
Rhexis Avatar answered Nov 01 '22 05:11

Rhexis


How do I recognize a record using reflection ?

As pointed out here and here

There is not only not an official way to do this, it is explicitly against the design of the feature. The intent for records is that, hopefully with C# 10, we'll get to a point where making a class a record is purely a convenience choice, and that every other part of the feature will be achievable through some form of syntax. It should not be a breaking change to change a type from a record to a class, and we even imagine that an IDE refactoring could automatically move a type to and from record syntax without clients noticing. For C# 9 there are some places where we didn't quite achieve this, but that's the goal.

Despite the above there are still scenarios where checking for record us useful. Some hackish ways to detect records which work ATM are:

  1. check if there is an EqualityContract property with the CompilerGenerated attribute
isRecord = ((TypeInfo)t).DeclaredProperties.Where(x => x.Name == "EqualityContract").FirstOrDefault()?.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is object;
  1. check for <Clone>$ member as pointed out by @Yair Halberstadt
isRecord = t.GetMethod("<Clone>$") is object;

or a combination of both

Is it possible to have a generic constraint that a generic type is a record ?

No

like image 4
kofifus Avatar answered Nov 01 '22 06:11

kofifus