简体   繁体   中英

Can I use a collection initializer for an Attribute?

Can an attribute in C# be used with a collection initializer?

For example, I'd like to do something like the following:

[DictionaryAttribute(){{"Key", "Value"}, {"Key", "Value"}}]
public class Foo { ... }

I know attributes can have named parameters, and since that seems pretty similar to object initializers, I was wondering if collection initializers were available as well.

Update: I'm sorry I'm mistaken - pass array of custom type is impossible :(

The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  1. One of the following types: bool, byte, char, double, float, int, long, short, string .
  2. The type object.
  3. The type System.Type.
  4. An Enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility (Section 17.2).
  5. Single-dimensional arrays of the above types. source

Source: stackoverflow .

You CAN DECLARE passing an array of a custom type:

class TestType
{
  public int Id { get; set; }
  public string Value { get; set; }

  public TestType(int id, string value)
  {
    Id = id;
    Value = value;
  }
}

class TestAttribute : Attribute
{
  public TestAttribute(params TestType[] array)
  {
    //
  }
}

but compilation errors occur on the attribute declaration:

[Test(new[]{new TestType(1, "1"), new TestType(2, "2"), })]
public void Test()
{

}

Section 17.1.3 of the C# 4.0 specification specifically does not allow for multidimensional arrays inside the attribute parameters, so while Foo(string[,] bar) might allow you to call Foo(new [,] {{"a", "b"}, {"key2", "val2"}}), it is unfortunately, not available for attributes.

So with that in mind, a few possibilities to approximate what you want are:

  1. Use a single-dimensional array, with alternating key and value pairs. The obvious downside to this approach is that it's not exactly enforcing names and values.

  2. Allow your parameter to appear multiple times by tagging your attribute definition with the following attribute:

     [AttributeUsage(AllowMultiple=true)] 

    In this way, you can now define:

     [KeyVal("key1","val1"), KeyVal("key2","val2")] public class Foo { ... } 

    This is a bit wordier than what I'm sure you were hoping for, but it makes a clear delineation between names and values.

  3. Find a JSON package and provide an initializer for your attribute. The performance hit is inconsequential as this is done during code initialization. Using Newtonsoft.Json, for instance, you could make an attribute like so:

      public class JsonAttribute : Attribute { Dictionary<string, string> _nameValues = new Dictionary<string, string>(); public JsonAttribute(string jsoninit) { var dictionary = new Dictionary<string, string>(); dynamic obj = JsonConvert.DeserializeObject(jsoninit); foreach(var item in obj) _nameValues[item.Name] = item.Value.Value; } } 

    Which would then allow you to instantiate an attribute like so:

     [Json(@"{""key1"":""val1"", ""key2"":""val2""}")] public class Foo { ... } 

    I know it's a little quote-happy, a lot more involved, but there you are. Regardless, in this crazy dynamic world, knowing how to initialize objects with JSON isn't a bad skill to have in your back pocket.

The short answer is no.

Longer answer: In order for a class to support collection initializers, it needs to implement IEnumerable and it needs to have an add method. So for example:

public class MyClass<T,U> : IEnumerable<T>
{
    public void Add(T t, U u)
    {
    }

    public IEnumerator<T> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

I can then do this:

var mc = new MyClass<int, string> {{1, ""}, {2, ""}};

So using this, let's try to make it work for an attribute. (side note, since attributes don't support generics, I'm just hardcoding it using strings for testing) :

public class CollectionInitAttribute : Attribute, IEnumerable<string>
{
    public void Add(string s1, string s2)
    {

    }

    public IEnumerator<string> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

And now to test it:

[CollectionInit{{"1","1"}}]
public class MyClass
{

}

and that doesn't compile :( I'm not sure where the limitation is exactly, I'm guessing attributes aren't newed up the same way a regular object is and therefore this isn't supported. I'd be curious if this can theoretically be supported by a future version of the language....

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM