записи C# 9.0 - отражение и общие ограничения
Два вопроса о новых записях:
Как распознать запись с помощью отражения? глядя сюда, может быть, есть способ обнаружить
EqualityContract
но я не уверен, что это путь?Возможно ли иметь общее ограничение, что универсальный тип является записью? то есть можно ли указать, что параметр типа T должен быть классом записи с использованием ограничения?
Обоснование - я пытаюсь написать код, чтобы проверить, является ли тип "данными" в функциональном смысле, то есть (1) неизменяемым и (2) имеет семантику значений.
Я начинаю с небольшого списка основных / основных / занесенных в белый список типов, которые являются "данными" (например, "int" или "DateTime"). Чтобы проверить неизменность, я могу проверить, что все поля и автоматические реквизиты доступны только для чтения / без сеттера и сами являются "данными" (начиная с приведенного выше списка) и т. Д.
Проверка семантики значения сложнее, однако я могу разрешить только указанные выше типы + записи, содержащие их как record
семантика гарантийного значения (я могу проверить, нет ли определенных пользователем 'Equals' и 'GetHashCode'). Вот почему мне нужно проверить, является ли тип записью.
6 ответов
Как уже упоминалось всеми, невозможно написать
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
Если вы попробуете записать классы в sharplab.io
вы увидите, что классы записи - это обычные классы, реализующие IEquatable<T>
интерфейс и содержат дополнительные члены, которые используются для сравнения и клонирования экземпляров класса записи. Нет никаких атрибутов, указывающих, что класс являетсяrecord class
.
Поэтому я считаю, что с помощью отражения невозможно определить, является ли класс классом записи.
Второй вопрос немного непонятен.
Если вы спросите, можно ли использовать дженерики с классами записей и указать для них ограничения, то ответ - ДА, это возможно. Классы записи - это обычные классы, и с ними можно использовать дженерики (опять же, вы можете попробовать это в sharplab.io
).
Если вы спросите, можно ли указать этот параметр типа T
Должен быть класс записи, тогда я считаю, что ответ - НЕТ. Страница предложения записей не содержит никакой информации об указании параметра универсального типа.T
должен быть класс записи.
В качестве "взлома" все записи имеют синтезированный метод
<Clone>$
который вы можете искать. Поскольку вы не можете написать метод с таким именем на C#, класс с
<Clone>$
член гарантированно будет записью начиная с C# 9.
Однако нет никаких гарантий, что так будет и дальше. Например, возможно, что в C# 10.0 некоторые записи не будут иметь
<Clone>$
член, или что некоторые не записи будут.
public static bool IsRecord(Type type) => type.GetMethod("<Clone>$") != null;
Как распознать запись с помощью отражения?
Это не только не официальный способ сделать это, но и явно противоречит дизайну функции. Намерение для записей состоит в том, чтобы, надеюсь, с C# 10 мы дойдем до точки, когда создание класса записью будет исключительно удобным выбором, и что любая другая часть функции будет достигнута с помощью некоторой формы синтаксиса. Изменение типа с записи на класс не должно быть критическим изменением, и мы даже представляем, что рефакторинг IDE может автоматически перемещать тип в синтаксис записи и обратно, незаметно для клиентов. Для C# 9 есть места, где мы этого не совсем достигли, но это цель.
Несмотря на вышесказанное, пока нет четкого способа проверить, был ли тип автоматически сгенерирован
Equals
и
GetHashCode
моя цель выше не может быть достигнута без определения того, является ли тип записью. Там открытый запрос, что здесь
Вот некоторые хакерские способы обнаружения записей, с которыми работает банкомат:
- проверьте, есть ли
EqualityContract
собственность сCompilerGenerated
атрибут
isRecord = ((TypeInfo)t).DeclaredProperties.Where(x => x.Name == "EqualityContract").FirstOrDefault()?.GetMethod?.GetCustomAttribute(typeof(CompilerGeneratedAttribute)) is object;
- проверить
<Clone>$
член, как указано @Yair Halberstadt
isRecord = t.GetMethod("<Clone>$") is object;
или комбинация обоих
Возможно ли иметь общее ограничение, что универсальный тип является записью?
Нет
Кажется, очень разумный вопрос, несмотря на некоторые ответы здесь. Реальность такова, что запись — это просто синтаксический сахар над классом, и ее можно создать множеством других способов.
Одним из вариантов может быть создание пустого базового класса или интерфейса записи маркера, даже если он пуст, и добавление к нему ограничения. Это обеспечит требуемое ограничение.
Если вы наследуете от этого класса все классы, которые хотите включить в ограничение, это будет работать нормально.
isRecord = t.GetMethod("$") - объект; ==> isRecord = t.GetMethod("$") не равно нулю;