Replies: 27 comments 7 replies
-
I know the reasoning for some of this, and at the same time don't pretend to be an expert on the subject matter. I for one think this would be really cool functionality to allow some better support for conventional stuff. I don't see myself trying to put complex logic inside of the lambdas but would love to be able to do stuff like
I've seen people simulate this with the method name as a hard coded string... and you could do things like make sure those are re-used constants, but seems like even basic support for soem form of lambda would be huge. |
Beta Was this translation helpful? Give feedback.
-
If we were going to do something, I wish we could have It would be a big new language feature though. And it would find great use in places other than lambdas- testing frameworks, binding/mapping frameworks, etc. |
Beta Was this translation helpful? Give feedback.
-
Even [SomeAttribute(SomeRecord(SomeOtherRecord(1)))]
void M(){} afaik this is possible in Java, but it requires all nested objects to be annotations as well. |
Beta Was this translation helpful? Give feedback.
-
It's not true if your logic in the attribute itself. Simple example : action filter in MVC that takes a predicate to validate. You don't need reflection inside What's about other types? DateTime/TimeSpan/Decimal are candidates number one for being attribute members. Yet another question: what's about Expressions inside attributes? For example: [SomeAttribute(() => true]
void M(){}
public class SomeAttribute : Attribute
{
public SomeAttribute(Expression<Func<bool>> expression)
{
}
} |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@Joe4evr but you can have const decimal :) Of course, on an IL level it's just a compiler trick and it can be changed in some dark magic ways via reflection, but it's quite useful for fair use, just like readonly fields are. It's always an option to store it in in InvariantCulture string representation and just (de)serialize when needed. It's all up to compiler, probably it how it's working in VB. Decimal may be stored just like a byte array (as it does today for consts IIRC) |
Beta Was this translation helpful? Give feedback.
-
You can only have "constants" of |
Beta Was this translation helpful? Give feedback.
-
@HaloFour what if compiler just generates the constructor that accepts byte array and perform all transformations? For example: [AttribuAttribute(10)]
class Foo { }
class AttribuAttribute : Attribute
{
public AttribuAttribute(decimal value)
{
// user code here
}
} becomes: [AttribuAttribute(new byte[] {01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0A, 00, 00, 00, 00, 00})]
class Foo { }
class AttribuAttribute : Attribute
{
public AttribuAttribute(byte[] value) : this(CompilerSerivce.ConvertSomehow(value))
{
}
public AttribuAttribute(decimal value)
{
// user code here
}
} |
Beta Was this translation helpful? Give feedback.
-
I do feel the need to point out that the CLR spec change required is small, and the C# compiler doesn’t have to do much. A lambda expression translates to a static method and then (in IL) it’s just a metadata token, which is an integer. This much is already in the C# compiler right now. Thus, on the CLR side, the only extensions required are 1) to allow metadata tokens as arguments to an attribute constructor parameter that expects a delegate, and 2) have it instantiate the relevant delegate type when it invokes the custom attribute constructor. The delegate type is already available (because it’s the type of the parameter) and the CLR already has access to a delegate constructor that accepts a metadata token (which is hidden from C#). On the C# side, the compiler only needs to serialize the (already existing) metadata token when serializing the attribute constructor arguments. There is no complex serialization of an entire method or expression tree necessary on neither the C# or the CLR side. |
Beta Was this translation helpful? Give feedback.
-
I would just like to throw in my +1 on this - it would be incredibly useful. I know from other discussions that this is probably a fairly low priority item for the team, but this would open up SO MANY possibilities that aren't immediately obvious...the community would explode with libraries that make use of this to enable advanced meta programming scenarios that aren't possible to do cleanly at the moment. |
Beta Was this translation helpful? Give feedback.
-
Not sure where this stands, but I'll throw in my +1 as well. Another recent example: dynamic aspect-oriented programming using interception, for logging. I can construct an interceptor that, for instance, logs method errors, like this: [LogThis]
public object MyMethod( object input )
{}
// ...
public class LogThisAttribute : InterceptAttribute
{
// ...
public override IInterceptor CreateInterceptor(IProxyRequest request)
{
// do logging here
} ...but if I wanted to vary the approach there's no way to do that from the attribute. It would be nice to be able to do something like this: [LogThis( (className, methodName) => $"{className} {methodName} invocation" )] Makes that attribute much more flexible. |
Beta Was this translation helpful? Give feedback.
-
Adding a bit more on my desire for this. I actually would prefer/require it be a lambda expression (or at least support that) since that is the main desire the platforms/frameworks/tools I create/maintain for my work would greatly desire. public class ResourceAttribute<TResource> : Attribute
{
public ResourceAttribute(LambdaExpression<Func<TResource, TProperty>> expression)
{
//basically get the PropertyName of TResource for future code-gen magic, or pure reflection if not.
// https://github.com/dotnet/roslyn/issues/19505
}
}
public class FooBar
{
[ResourceAttribute<RealResourceManagerType>(rm => rm.SomeRelatedResourceProperty)]
public string SomeMember { get; set; }
} Leveraging assumed "Generic Attributes" #124 although similar-but-more-obtuse could be done with just the lambda expression (thus also parse/figure out the TResource or such) I remember discussing over a year ago with someone closer to the compiler and assembly CIL/assembly format that "Encoding an ExpressionTree might be simpler than a delegate, especially with respect to referencing other types." I could be wrong here though, anyone know much for certain? With an (Lambda)Expression whoever wants executable code can I also have quite a number of reasons for wanting this that involve simple validators relying on properties (not just "in range of constants" or "is positive"), that others have mentioned here-there but I don't see a code example directly: public class FooViewModel
{
public int SomeNumber_A { get; set; }
// B must be null, or larger than A, enforced elsewhere in semi-custom validation middle-layer
[Validation<FooViewModel>(e => e.SomeNumber_B == null || e.SomeNumber_B > e.SomeNumber_A)]
public int? SomeNumber_B { get; set; }
} |
Beta Was this translation helpful? Give feedback.
-
@admalledd if im not mistaken expressions (with compile() - without it should work but thats barely useful) wouldnt work in AOT scenarios Another problem is that these expressions got discontinued (a shame but cest la vie) and will not be evolved in future meaning that supporting/relying on it is likely a bad idea |
Beta Was this translation helpful? Give feedback.
-
They will, using an interpreter. That might not have the performance that you need, but it does work.
It's true they're not being kept up-to-date with language additions, but that doesn't mean they're discontinued or that they shouldn't be used. They are still a supported part of the language. |
Beta Was this translation helpful? Give feedback.
-
@svick ah right but nowadays interpreter is also incomplete if i remember correctly (at least i believe mono interpreter used to be lacking some features since it didnt mirror 100% traditional reflection maybe thats has changed since then) probably could be completed but since it wasnt done from the start theres high chance its not trivial. And of course performance hit is nontrivial too And regarding discontinuity - i used this word on purpose instead of obsolete. Its supported in runtime but it was announced in old dotnet corefx its not going to be evolved meaning lack of certain features forevermore (refs for example from the top of my mind and any future ones like shapes) unless this package gets unfreezed and be back on the menu Edit More specifically it was done in old corefx and got freezed along with various other packages that they believed to be mature enough that evolving is no longer necessary for them |
Beta Was this translation helpful? Give feedback.
-
+1 In Unity, we often use an attribute to automatically search and cache all assets containing a particular class in a serialized form. This works well for a dumb search, but things start to get complicated when mixed in with those assets are "templates" - assets we derive from to streamline new asset creation. We don't want to include the "template Bullet" in the list of all Bullets to be used in the final game, for example. This calls for some sort of filter in the auto cache attribute itself. We could make our own filter condition as a field in the auto-cache attribute class, but this could balloon the auto-cache attribute class really quick as more asset types require different filter conditions, plus it would destroy the re-usability of the class itself. If we can just pass a lambda into the attribute constructor, most of this boilerplate can be reduced and promotes DRY principles. |
Beta Was this translation helpful? Give feedback.
-
+1 how come this is not on NET6 already?, attributes are awesome, easy to read and reduce syntax, any improvement to them its a big plus for me |
Beta Was this translation helpful? Give feedback.
-
One cool thing this feature will allow is with I hope this becomes a feature on .NET 7. |
Beta Was this translation helpful? Give feedback.
-
Strongly typed references to an objects properties without using a string. Similar to validation examples above, I care less about providing actual expressions as parameters to an attribute, but more just being able to point at a property of the type. Eg. [Except(x=>x.Age)] // this is just better in so many ways than "Age" as a parameter I get that I could just have attributes that get assigned to the properties themselves, but the idea here would be that this attribute would be applied at the class level to a class inheriting from another class I wouldn't want to pollute with attributes at the parent level. If there are already ways to handle this, I'm all for that as well. |
Beta Was this translation helpful? Give feedback.
-
Why does this issue not have any labels? Does no one care about this? |
Beta Was this translation helpful? Give feedback.
-
+1 from me with a use case: I have a data class and I want to write tests for it. I could make a bunch of methods on the class, or put the tests in one big test method, or write a separate test driver, or use a test library. But if attributes could have lambda expressions I could simply put an attribute for each assertion on the class and use reflection to check them all. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true, Inherited = true)]
public class AssertAttribute<T>(Predicate<T> predicate, string message) : Attribute
{
public Func<T, string> Validate { get; } = obj => predicate(obj) ? string.Empty : message;
}
[Assert<MyData>(data => data.MinFoo <= data.MaxFoo, "Min must not be greater than max.")]
[Assert<MyData>(data => !string.IsNullOrEmpty(data.Name), "Name cannot be null or empty.")]
public class MyData
{
public string Name { get; set; }
public int MinFoo { get; set; }
public int MaxFoo { get; set; }
} Elsewhere: public static class ValidationExtension
{
public static bool IsValid<T>(this T data, out string[] errors) =>
!(
errors = (
from assertion in typeof(T).GetCustomAttributes<AssertAttribute<T>>(true)
let message = assertion.Validate(data)
where !string.IsNullOrEmpty(message)
select message
).ToArray()
)
.Any();
} In use: MyData invalid = new() { MinFoo = 5, MaxFoo = 10 };
if (!invalid.IsValid(out string[] messages))
throw new ValidationException(messages); |
Beta Was this translation helpful? Give feedback.
-
+1 I'm working on creating attribute-based filters for EF Core models. While my main goal is to implement multiple filters per single entity capability to EF Core, I am missing the possibility to add lambdas into attributes' constructors. Currently, it is working as follows: [EntityFilter(typeof(ILastname), nameof(FilterExpression1))]
[EntityFilter<ILastname>(nameof(FilterExpression2))]
public interface ILastname
{
string Lastname { get; set; }
static Expression<Func<ILastname, bool>> FilterExpression1
= x => x.Lastname.EndsWith("bb");
static Expression<Func<ILastname, bool>> FilterExpression2
= x => x.Lastname.EndsWith("bbb");
} But I would like to have a third possibility as below: [EntityFilter<ILastname>(x => x.Lastname.EndsWith("bbbb"))]
... For that, possibility to have lambdas in attributes' constructor parameters is required. |
Beta Was this translation helpful? Give feedback.
-
+1 with another use case. It would be nice if it was possible to create a generic validation attribute that checks each element in a collection, with the lambda (or predicate etc.) as the parameter. This would allow you to leverage the ValidationAttribute which a lot of front-end libraries use. Simplified example: public class EmailMessage
{
[Required]
public string FromEmail { get; set; }
[Required]
[ListValidation<string>(Validate,"Must be valid email address")]
public List<string> ToEmails { get; set; }
[ListValidation<string>(Validate,"Must be valid email address")]
public List<string> CcEmails { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public string Body { get; set; }
public static bool Validate(string s)
{
//check email format...
}
}
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class ListValidationAttribute<T>(Predicate<T> validationHandler, string errorMessage)
: ValidationAttribute(errorMessage)
{
private Predicate<T> ValidationHandler { get; set; } = validationHandler;
public override string FormatErrorMessage(string requiredPropertyName)
{
return ErrorMessageString;
}
public override bool IsValid(object? value)
{
if (value != null)
{
if (value is ICollection list)
{
foreach (T o in list)
{
if (!ValidationHandler(o))
{
return false;
}
}
}
else
{
throw new InvalidOperationException("Parameter \"value\" must be of type ICollection");
}
}
return true;
}
} |
Beta Was this translation helpful? Give feedback.
-
@sirisian commented on Sat May 02 2015
Essentially allow lambdas as arguments to attribute constructors.
Code example. Specifically the "[Converter(s => s == "one" ? 1 : 0)]" line and "public ConverterAttribute(Func<string, int> converter)" in the constructor.
Possible use cases I've seen from years ago are converters for serialization libraries and alternative ways to define validator functions.
@HaloFour commented on Sat May 02 2015
C# is limited by the CLR in the types of the parameters that can be used for custom attributes. Those values need to be easily and predictably serialized/deserialized as they are embedded as a BLOB directly in the assembly metadata and reconstructed by the run time. The compiler could probably emit anything it wanted in the BLOB but the CLR itself can only understand and deserialize the integral types,
bool
,char
,string
,Type
(serialized as astring
of the type name) and arrays of those types.Given that, how would a lambda be embedded? They can't really be serialized, even if the limitation on the CLR could be lifted. Only thing I could think of is that the compiler generates a synthetic public type with a public static method and then serializes the method runtime handle. The attribute itself would have to be defined in a way that the property is really of
long
so that the CLR would deserialize the value of the method handle which would be converted to aRuntimeMethodHandle
andMethodBase.GetMethodFromHandle
called to obtain theMethodBase
which is then converted into a delegate, but how to make that seamless sounds messy.@MaulingMonkey commented on Wed Mar 15 2017
"Only thing I could think of is that the compiler generates a synthetic public type with a public static method"
This is basically how lambdas already work. For:
Roughly the following is generated:
Multiple lambdas may be collapsed into a single class (depending on if they share the same captured state? - none in this case...)
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter can currently (de)serialize delegates - presumably by persisting the object instance and method name or similar, although this is potentially rather brittle. A less general purpose method that might work here (by virtue of being unable to capture local method state when constructing attributes, and thus always being able to generate a static delegate instance to reference) would be to simply persist the name of the static field ("<>c.<>9__0_0").
Beta Was this translation helpful? Give feedback.
All reactions