user276648
user276648

Reputation: 6383

C# attribute with complex structure

EDIT: looks like it's not possible to have only one attribute with everything inside like solution #2, so I ended up using solution #3 which is not error prone unlike solution #1.

I have an attribute as follow:

// Solution #1 (working)
[Module(Name="Module1", Guid="xxxxx",
  NeededFiles_Name = new string[]
  {
    "module1Conf.xml",
    "module1.sql",
    "resourcesWindows",
    "resourcesUnix",
  },
  NeededFiles_OS = new OSType[]
  {
    All,
    All,
    Windows,
    Unix,
  }
  NeededFiles_Component = new ComponentType[]
  {
    Server,
    Server,
    All,
    All
  }
)]

The problem is that it's not easy to fill the NeededFile_Xxx properties. It would be easier to have only one property like

// Solution #2 (NOT working)
[Module(Name="Module1", Guid="xxxxx",
  NeededFile = new FileInfo[]
  {
    new FileInfo("module1Conf.xml", All, Server),
    ...
  }
)]

but it's not allowed by the compiler (NeededFile is not a valid named attribute argument because it is not a valid attribute parameter type.

I could also divide my attribute into severals and do

// Solution #3 (working)
[Module(Name="Module1", Guid="xxxxx"]
[NeededFile("module1Conf.xml", All, Server)]
[NeededFile("module1.sql", All, Server)]
[NeededFile(...)]

but I'd rather keep everything in the same.

Is it possible to do it? Is there a better way to do it?

Upvotes: 3

Views: 4343

Answers (1)

Drew Shafer
Drew Shafer

Reputation: 4802

Why isn't it (you second option) allowed? I just tried the following:

public class MyFileInfo
{
    public MyFileInfo(string fname, string somethingElse, string anotherThing);
}

public class ModuleAttribute : System.Attribute
{
    public ModuleAttribute(string Name, string Guid, params MyFileInfo[] myFileInfoList)
    {
    }
}

[Module("Module1", "xxxx",
    new MyFileInfo("a", "b", "c"),
    new MyFileInfo("a", "d", "f"))
]
public class TestClass
{
}

And the compiler didn't complain. I think you can achieve pretty much exactly what you're trying to do.

Edit: that's what I get for letting intellisense do my compiling for me in the middle of the night after a couple beers. As you (and I) have discovered, Attribute arguments must be constants, or lists of constants - a restriction that explicitly excludes classes and structs.

I think the best you're going to be able to do is pass in a serialized version of the data you're trying to tag onto the attribute. Then the attribute constructor can do the actual object initialization at runtime. This loses compile-time checking of your initializers, but gets you a bit closer to the compactness of code you were looking for.

public class MyFileInfo
{
    string fname;
    string anotherThing;
    string somethingElse;
    public MyFileInfo(string serializedFileInfo)
    {
        string[] parts = serializedFileInfo.Split(',');
        fname = parts[0];
        anotherThing = parts[1];
        somethingElse = parts[2];
    }
    public static implicit operator MyFileInfo(string things)
    {
        return new MyFileInfo(things);
    }
}

public class ModuleAttribute : System.Attribute
{
    List<MyFileInfo> fiList;
    public ModuleAttribute(string Name, string Guid, params string[] serializedFileInfoList)
    {
        fiList = serializedFileInfoList.Select(s => new MyFileInfo(s)).ToList();
    }
}

[Module("Module1", "xxxx",
    "a,b,c",
    "d,e,f"
    )
]
public class TestClass
{
}

Really though, I think you should just handle it through a separate attribute for each file.

Upvotes: 7

Related Questions