C# 9.0记录 - 反射和泛型约束

20

关于新记录功能有两个问题:

  1. 如何使用反射识别记录?在这里查看[1],也许可以检测EqualityContract,但我不确定是否应该采用这种方式?

  2. 是否可能有一个通用的约束条件,即通用类型是记录?也就是说,如果可以使用约束指示类型参数T必须是记录类吗?


有什么不清楚的吗? - kofifus
3
为什么你要问这个问题?首先,记录(record)仍然是一个类(class)。至于第二个问题,你是什么意思?你是不是想指定 T 是一条记录(record)?它只是一个类,所以可能不能。 - Panagiotis Kanavos
3
目前在[charplang的GitHub中有一场热烈的讨论,涉及到一个冠军记录下的话题。从这条评论中可以看出,有意见认为不能明确地告诉一个类是否是一个记录类型。在C# 10中,“记录”和“类”之间将没有实质性的语义区别。请点击链接查看并了解更多详情。©kofifus - Peter Csala
1
只是提供信息,最近开了一个问题[#4121](https://github.com/dotnet/csharplang/issues/4121),以确定类型是否为记录。 - Pavel Anikhouski
1
我认为这是一个很棒的问题,即使答案是“你不能”。我只是在想一个相关的问题:有没有时间不应该使用记录而是类?这里的答案似乎也适用于那个问题:你无法分辨两者之间的区别,所以你应该坚持使用类的唯一时间,我想,就是如果记录的内置功能(例如成员等号)与您的意图冲突。 - Ryan Lundy
显示剩余2条评论
6个回答

16
正如其他人所提到的,不可能编写
private void MyFunc<T>(T t) where T : record {...}

您可以创建一条记录,然后让您创建的每个记录类型都继承自该记录。这基本上可以实现您所要求的功能,但是我不确定我的感觉如何...

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

使用此方法,您只能传递记录类型,因为类无法继承自记录。

MyFunc(new MyRecord()); // Works
MyFunc(new MyClass());  // Compiler Error

这真的很棒。我想指出以下工作: public abstract record Message; public record TestMessage(string Msg, double Val) : Message; var m = TestMessage("msg", 5d); 仍然保持单行记录定义,这让我对这种方法印象深刻。除了类型约束不说记录而是 Message(或其他),因此可读性稍差外,没有任何缺点。 - HaileyStorm
在针对 net 6 的 VisualStudio 17.5.2 中无法工作。尝试使用基础记录为抽象记录和记录。两者都会产生相同的错误消息:“从 'T' 到 '<namespace>.<MyBaseRecord>' 没有隐式引用转换”。 - Andrew Gray

15
如何使用反射识别记录?在sharplab.io中尝试记录类,您会发现记录类是实现IEquatable<T>接口的普通类,并包含用于比较和克隆记录类实例的附加成员。没有特殊的属性表明该类是record class。因此,我认为使用反射无法确定类是否为记录类。
here,也许可以检测EqualityContract,但我不确定这是否是正确的方法?
使用反射可以确定一个类是否具有这样的属性,但这并不能百分之百地保证具有这种属性的类是记录类。
无法实现。
记录提案页面不包含有关指定泛型类型参数T必须是记录类的信息。
如果您阅读Champion records页面下此comment中的讨论,您将了解在C#9中没有办法指定类似于where T:record的内容。此外,计划消除记录和类之间任何有意义的语义差异,在C#10中,记录功能(如with)也将对类可用。添加record约束将使这个目标无法实现。

1- "所以我认为使用反射无法确定一个类是否是记录类" - 你知道哪里可以得到明确答案的地方吗? 2- 是的,我的意思是“是否有可能指示类型参数T必须是记录类”,所以我想答案是否定的。 - kofifus
也许反射可以检测到EqualityContract? - kofifus
也许 csharplang 开发团队有关于添加问题 #1 和 #2 中所提到的功能支持的额外信息(或见解)。因此,我认为最好直接向 csharplang 团队在 C# 的 Github 仓库 上提出这个问题。 - Iliar Turdushev
也许反射能够检测到EqualityContract吗?虽然有可能判断一个类是否拥有这样的属性,但我认为这并不能百分之百地保证该类是记录类。 - Iliar Turdushev

10

作为一个“hack”,所有的记录都有一个合成方法<Clone>$,你可以查找它。由于你无法在C#中写一个这样的名称的方法,一个带有<Clone>$成员的类被保证是一个记录,至少在C# 9中是这样。

然而,不能保证这种情况将会持续下去。例如,在C# 10.0中,一些记录可能没有<Clone>$成员,或者一些非记录类型可能会有。

public static bool IsRecord(Type type) => type.GetMethod("<Clone>$") != null;

2
我不知道为什么会这样做,但理论上使用 Reflection.Emit,你可以让别人创建一个具有 <Clone>$ 方法且不是记录的对象。 - nalka
@nalka 当然,你也可以用其他语言创建这样的对象 - 但这对编译器生成的所有内容都是正确的,即使它们有一个“官方”的检查方式。 - Yair Halberstadt

4
如何使用反射识别记录?
正如这里这里所指出的,

目前没有官方方法来实现这一点,这甚至是该功能设计上明确反对的。对于记录的意图是,希望通过某种形式的语法实现每个记录功能,以便在 C# 10 中将类设置为记录仅是一种方便的选择。不应该通过将类型从记录更改为类来破坏其他部分的记录特性,我们甚至可以想象IDE重构可以自动移动类型到和从记录语法中而没有客户端察觉到。对于C# 9,有一些地方我们并未完全实现这一点,但这是目标。

尽管如上所述,仍然存在一些场景需要检查记录。目前可用的一些hack方式来检测记录包括:
  1. 检查是否有一个带有CompilerGenerated属性的EqualityContract属性
isRecord = ((TypeInfo)t).DeclaredProperties.Where(x => x.Name == "EqualityContract").FirstOrDefault()?.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is object;
  1. 检查<Clone>$成员,正如@Yair Halberstadt所指出的。
isRecord = t.GetMethod("<Clone>$") is object;

或两者的组合

是否可能有一个泛型约束,使泛型类型成为一条记录?

不可能


1
寻找<Clone>$是MVC绑定检测记录需要构建的方式。https://github.com/dotnet/aspnetcore/blob/c925f99cddac0df90ed0bc4a07ecda6b054a0b02/src/Mvc/Mvc.Core/src/ModelBinding/Metadata/DefaultBindingMetadataProvider.cs#L101 - Jeremy Lakeman

1
似乎这是一个非常合理的问题,尽管这里有一些答案。事实上,记录只是在类上的语法糖,可以通过许多其他方式创建。
其中一种选择是创建一个空的标记记录基类或接口,即使是空的,也要添加约束。这将实现所需的限制。
只要您从所有希望包含在约束中的类继承这些类,那么就可以正常工作。

-2
isRecord = t.GetMethod("<Clone>$") is object; ==> isRecord = t.GetMethod("<Clone>$") is not null;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接