How to Target Attributes for a Record Class

How do I target attributes for a record class?

To target the various parts of the expanded class, use the appropriate attribute target. For instance:

// Target the property, use `property`
record Person(string FirstName, string LastName, [property: JsonIgnore] int Age);

// Target the backing field of the property, use `field`
record Person(string FirstName, string LastName, [field: JsonIgnore] int Age);

// Target the constructor parameter, use `param`
record Person(string FirstName, string LastName, [param: SomeParamAttribute] int Age);

Positional record attributes in ASP.NET Core

From the proposal specification:

Attributes can be applied to the synthesized auto-property and its backing field by using property: or field: targets for attributes syntactically applied to the corresponding record parameter.

Try next:

public record InputModel(
string Email,
[property:DataType(DataType.Password)] string PasswordB,
[property:Display(Name = "Remember me?")] bool RememberMe);

Based on sharplab.io decompilation, the attributes should be emitted for generated properties.

Without this target the attribute gets emitted as attribute on corresponding record constructor parameter.

JsonProperty on C# Records in Constructor

Per the docs:

Attributes can be applied to the synthesized auto-property and its backing field by using property: or field: targets for attributes syntactically applied to the corresponding record parameter.

So you want

record Something([property:JsonProperty("hello")] string world) {}

Without the property: qualifier, the attribute ends up on the parameter of the generated constructor (which is useful in other scenarios, like nullability).

Best way to access attributes

Attribute.GetCustomAttribute and PropertyInfo/MemberInfo.GetCustomAttribute is the recommended way of getting at attribute objects.

Although, I wouldn't normally enumerate all properties with attributes; you generally want to work a particular attribute so you'd just call GetCustomAttribute directly.If you're looking for attributes on any of your properties, enumerating those properties looking for attributes based on GetCustomAttribute() the way you're doing it, is the best way to do it.

C# 9 record XmlAttribute serialization attribute ignored

In your code, you are setting the attribute to the constructor argument

public record Person([XmlAttribute("dte_created")]DateTime CreatedAt);

You need to instruct the compiler to set the attribute on the property using property::

public record Person([property: XmlAttribute("dte_created")]DateTime CreatedAt);

Python: Get a list of attributes used by a method

class Tracker:
"""
Tracks the attribute access right after `start_track` is set to True.

Add this to __metaclass__ for any class that you need to track attributes for given a
target method.
"""
attrs_to_ignore = []
attr_used = []
start_track = False

def __getattribute__(self, item):
"""
Inspect getter for tracking the attributes accessed.
"""
if item not in ['on_access']:
self.on_access(item)
return super().__getattribute__(item)

def __setattr__(self, key, value):
"""
Inspect setter for tracking the attributes accessed.
"""
if self.start_track:
self.on_access(key)

super().__setattr__(key, value)

def on_access(self, key):
"""
on attribute access, record attribute if and only if its not from
core attribute or `attrs_to_ignore` set to class.
"""
if key in ['start_track', 'attr_used', 'on_access', 'attrs_to_ignore'] or key in self.attrs_to_ignore:
return
if self.start_track:
self.attr_used.append(key)

def attr_used_by_method(cls, method_name, method_params):
"""
Tracks the access to attributes within an execution.
"""
# create meta class with the above tracker and `cls` from parameters.
tracker = type('Tracker', (Tracker, cls), dict(cls.__dict__, **Tracker.__dict__))()
tracker.attrs_to_ignore = [func for func in dir(tracker) if callable(getattr(tracker, func))]
if hasattr(tracker, method_name):
candidate_method = getattr(tracker, method_name)
# Now, we want to track attributes.
tracker.start_track = True
candidate_method(**method_params)
attr_used = tracker.attr_used.copy()
tracker.attr_used = []
return attr_used
else:
# Error class/obj do not have that method.
return 1

Bytecode transforming record class to be mutable

Your question posits a false dichotomy. Records are a language feature, with some degree of JVM support (primarily for reflection), but that doesn't mean that the JVM will (or even can) enforce all the requirements on records that the language requires. (Gaps like this are inevitable, as the JVM is a more general computing substrate, and which services other languages besides Java; for example, the JVM permits methods to be overloaded on return type, but the language does not.)

That said, the behavior you describe is a pretty serious party foul, and those who engage in it should be shamed out of the community. (It is also possible they are doing so out of ignorance, in which case they might be educable.) Most likely, these people think they are being "clever" in subverting rules they don't like, but in the process, poisoning the well by promoting behaviors that users may find astonishing.

EDIT

The author of the transformer posted some further context here about what they were trying to accomplish. I'll give them credit for making a good faith effort to conform with the semantics of records, but it undermines the final field semantics, and only appears to work for records that do not customize the constructor, accessors, equals, or hashCode methods. This describes a lot of records, but not all. This is a good cautionary tale; even when trying to preserve the semantics of a class while transforming it, it is easy to make questionable assumptions about what the class does or does not do that can get in the way.

The author waves away the concern about the final field semantics as "not likely to cause a problem." But this is not the bar. The language provides certain semantics for records. This transformation undermines those semantics, and yet still tells the user they are records. Even if they are "minor" and "unlikely", you are breaking the semantics that the Java language promises. "99% compatible" rounds to zero in this case. So I stand by my assertion that this framework is taking inappropriate liberties with the language semantics. They may have been well-intentioned, they may have tried hard to not break things, but break things they did.



Related Topics



Leave a reply



Submit