I am designing a custom attribute class.
public class MyAttr: Attribute
{
public ValueRange ValRange { get; set; }
}
Then I am attempting to assign this attribute to a property in an adjoining class:
public class Foo
{
[MyAttr(ValRange= new ValueRange())]
public string Prop { get; set; }
}
However, the compiler is complaining the following:
'ValRange' is not a valid named attribute argument because it is not a valid attribute parameter type
I also tried converting the ValueRange class to a struct in hopes that become a value type might solve the problem. Is there any way around this?
Is there any way around this?
No.
For more details I refer you to section 17.1.3 of the C# 4 specification, which I reproduce here for your convenience:
The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:
One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
The type object.
The type System.Type.
An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility.
Single-dimensional arrays of the above types.
A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.
Remember, the point of an attribute is to at compile time add information to the metadata associated with the entity upon which you've placed the attribute. That means that all the information associated with that attribute must have a well-defined, unambiguous way to serialize it into and out of metadata. By restricting the set of legal types to a small subset of all possible types we ensure that the compiler can always emit legal metadata that the consumer can understand.
Attribute parameter values need to be resolvable at compile time (i.e constants).
See Attribute Parameter Types on MSDN:
Values passed to attributes must be known to the compiler at compile time.
If you can create a ValueRange that is a constant, you can use it.
Is there any way around this?
Yes.
You can have your attribute use a Type property and then use types that implement a defined interface, for which the code that processes that attribute would have to assume, and as such also create an implicit, but hopefully documented, requirement to its clients:
public interface IValueRange {
int Start { get; }
int End { get; }
}
public class MyAttr : Attribute {
// The used type must implement IValueRange
public Type ValueRangeType { get; set; }
}
// ....
public class Foo {
class FooValueRange : IValueRange {
public int Start { get { return 10; } }
public int End { get { return 20; } }
}
[MyAttr(ValueRangeType = typeof(FooValueRange))]
public string Prop { get; set; }
}
This is not unlike many classes in the System.ComponentModel namespace, like DesignerAttribute.
Attribute parameters must be values of the following types (quoting the article):
Simple types (bool, byte, char, short, int, long, float, and double)
string
System.Type
enums
object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
One-dimensional arrays of any of the above types
Edit: Changed "compile-time constant" to "value", since types and arrays are not constants (thanks to the commenter who pointed this out (and subsequently deleted his comment for some reason...))
Attributes can only receive compile-time-constants as parameters (e.g. 3, "hello", typeof(MyClass), "path to a resource defining whatever non constant data you need").
The last example (passing a type) I gave may help you design a workaround (pass a type implementing an interface with the method you need).
Related
I want to define a function with a parameter, that paramter must be a specific, defined in a list (for example)
i remember doing this years ago but my memory has failed me.
example
public void foo(specific choice list defined in my class){}
specific-list = x,y,z
consume it list this (i think)
foo(myclass.x)
To expand on #kaffekopp's comment, it seems likely that you are talking about an enum, i.e.
public enum MyEnum // declare the enum type (optional: specify an underlying integer type)
{
Apple, // define the choices (note: enums are **not** strictly enforced;
Pear, // they are just named integers, and at runtime you can be surprised to find
Bicycle, // that the actual value is 42, a value you never defined)
}
with (on MyClass):
public MyEnum SomeProp {get;set;} // define a property as being of that enum type
and:
public void Foo(MyEnum option) {...} // define a method that takes a parameter of the type
then either:
Foo(MyEnum.Bicycle); // passing a constant/literal
or:
MyClass obj = ...
Foo(obj.SomeProp); // passing an existing value from somewhere
I have this extension method for an Enum:
public static List<Enum> Values(this Enum theEnum)
{
return Enum.GetValues(theEnum.GetType()).Cast<Enum>().ToList();
}
I'm getting a code analysis violation:
CA1062 Validate arguments of public methods
In externally visible
method 'EnumExtensions.Values(this Enum)', validate parameter
'theEnum' before using it.
Why is that happening? How can I validate the parameter? I can't check for null because an enum is a non-nullable value type. Is there some other check that is supposed to be occurring here?
I can't check for null because an enum is a non-nullable value type.
Any particular enum is a value type, but Enum itself isn't. (Just like ValueType isn't a value type either... every type derived from ValueType except Enum is a value type.)
In other words, I could write:
Enum foo = null;
var bang = foo.GetValues();
That would compile and then fail at execution time with a NullReferenceException.
Given that you ignore the value except to get its type, I'd actually suggest removing it and either accepting a Type or making it generic in the type of enum you want. But if you want to keep the current signature, you just need:
if (theEnum == null)
{
throw new ArgumentNullException();
}
You might also want to look at my Unconstrained Melody project which provides a bunch of helper methods for enums, generically constrained to enum types via IL manipulation.
The enum keyword is used to declare an enumeration, a distinct type that consists of a set of named constants called the enumerator list.
It is used just for declare another enums. In any case, the input should be your declaration.
public enum theEnum {
enum1,
enum2
}
public void ShowEnum(theEnum e)
{
System.Console.WriteLine(e.GetType());
}
I have an interface for implementing an "output formatter" that looks a bit like this:
public interface IFormatOutput {}
public class HtmlOutputFormatter : IFormatOutput {}
public class TextOutputFormatter : IFormatOutput {}
// etc, etc...
public enum OutputFormat {
Html,
Text,
HappyMeal,
Excel
}
public class SomeFormattableEntity {
int Id { get; set; }
OutputFormat OutputType { get; set; }
}
So SomeFormattableEntity is persisted in a database via Dapper and its OutputType property is stored as the underlying integer value (ie, in an INT column). As you can guess, I want to provide an instance of a IFormatOutput to handle a SomeFormattableEntity based on its OutputType property.
Is there some clean best-practice way to handle this type of relationship? My ideas so far include a factory with innards potentially consisting of:
grandpa's horrible ugly switch statement
an array mapping the enum value to a Type
reflection-based magic mapping enum member name as string to class type elsewhere
some mapping mechanism involving attributes
I realize it is not desirable to require an instance of a thing whose type is based on a value, but it seems hard to avoid this when SQL is involved. Basically the problem is that multiple "things" that all have varying .NET types are stored in a single table. I keep running into this idiom and am unable to find an elegant solution to it.
I'd probably go for a custom attribute with a FormatsOutputFor property. Then decorate all of your implementations of IFormatOutput with the attribute. e.g.
[YourAttribute(OutputFormat.Html)]
public class HtmlOutputFormatter : IFormatOutput {}
Then in your factory:
// get all your formatters
var formatters = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(s => s.GetTypes())
.Where(p => Attribute.IsDefined(p, typeof(YourAttribute)));
// Now go through each formatter and use the attribute to figure out which
// output format it's for. Add these to some static IDictionary<OutputFormat, Type>
You probably want to build some internal cache that maps an OutputFormat value to a Type. Then your factory can double check that you have only got one type mapped to each output format and if you try to get a formatter for an enum value that doesn't have a corresponding class then you wont get some obscure TypeLoadException from activator.
Hopefully that makes sense...
How about:
OutputFormat format = OutputFormat.Excel;
object obj = Activator.CreateInstance("myAssemblyName", format.ToString());
Assuming the elements of your enum has the exact name of your types?
Is it possible when looking at a class' properties to detect if any of them is a reference type.
Take below as an example:
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ProgrammeClient
{
public int Id { get; set; }
public bool IsActive { get; set; }
public IClient Client { get; set; }
}
ProgrammeClient: -
Id and IsActive are properties but Client is a reference type. Is there a way of detecting this?
Many thanks,
Kohan.
Addendum
The reason i ask is: I am using a mapper that checks types are the same before matching property names and copying the values. My hope is to detect classes and override the type matching and simply copy the classes properties if the THEY type match.
Well, it sounds like you may be trying to detect the difference between a value type and a reference type. You can find that out using Type.IsValueType... but be aware that value types can easily have properties too. (Think about DateTime for example.) Also, some types which you may want to regard as "not objects" are reference types - string being a prime example.
Another option would be to use Type.IsPrimitive - is that what you're looking for? If so, you should be aware that decimal, DateTime and string are not primitive types.
If you can describe exactly what makes a type an "object" in your way of thinking (or rather, in whatever way makes a semantic difference in what you're trying to do with your type). I suspect you don't currently have a very clear set of criteria - coming up with those criteria may well clarify other aspects of your current task, too.
You can use a little reflection to see if a property is a value type or a class type. Class is probably what you mean by "object". All types in .NET derive from the object type.
Client.GetType().IsClass
Or you can loop through all properties and see which are compound
foreach(var p in ProgrammeClient.GetType().GetProperties())
{
if(p.PropertyType.IsClass) Console.WriteLine("Found a class");
}
Check if the type is a string and check if it is a class.
public static bool IsNonStringClass(this Type type)
{
if (type == null || type == typeof(string))
return false;
return typeof(Type).IsClass;
}
All properties in your example return objects, as everything is an object in .NET; int and bool are objects. If you mean a reference type, as opposed to value types, then you can do the following:
foreach (PropertyInfo pi in typeof(Client).GetProperties()) {
if (pi.PropertyType.IsClass) {
// reference type
// DoMyFunkyStuff
}
}
You can enumerate the properties via Reflection, and check them:
bool ContainsOnlyValues() {
return typeof(ProgrammeClient).GetProperties().All(x => x.PropertyType.IsValueType);
}
The Type.IsvalueType property can reveal this.
Id.GetType().IsValueType
This will be True for Id, false for a class
If using TypeSupport nuget package you can simply do:
typeof(ProgrammeClient).GetExtendedType().IsReferenceType;
TypeSupport does inspection and provides deeper insight on the capabilities of a given type, handling things like strings, enums etc and makes it easier to code these types of things.
It is really unbelievable but real. This code will not work:
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class Range : Attribute
{
public decimal Max { get; set; }
public decimal Min { get; set; }
}
public class Item
{
[Range(Min=0m,Max=1000m)] //compile error:'Min' is not a valid named attribute argument because it is not a valid attribute parameter type
public decimal Total { get; set; }
}
While this works:
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field)]
public class Range : Attribute
{
public double Max { get; set; }
public double Min { get; set; }
}
public class Item
{
[Range(Min=0d,Max=1000d)]
public decimal Total { get; set; }
}
Who can tell me why double is OK while decimal is not.
This is a CLR restriction. Only
primitive constants or arrays of
primitives can be used as attribute
parameters. The reason why is that an
attribute must be encoded entirely in
metadata. This is different than a
method body which is coded in IL.
Using MetaData only severely restricts
the scope of values that can be used.
In the current version of the CLR,
metadata values are limited to
primitives, null, types and arrays of
primitives (may have missed a minor
one).
Taken from this answer by JaredPar.
Decimals while a basic type are not a
primitive type and hence cannot be
represented in metadata which prevents
it from being an attribute parameter.
From the specs:
The types of positional and named parameters for an attribute class
are limited to the attribute parameter types, which are:
One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
The type object.
The type System.Type.
An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Attribute specification).
Single-dimensional arrays of the above types.
The answer to this problem is to use strings, which are allowed as attributes despite not being an atomic type. Don't use doubles as rounding will make the results less accurate.
public String MinimumValue
{
get
{
return minimumValueDecimal.ToString();
}
set
{
minimumValueDecimal = Decimal.Parse(value);
}
}
private decimal minimumValueDecimal;