I have not used attributes in my daily work before, but I read about them. I also did some tests to support what I will say here. If I'm wrong anywhere - feel free to tell me this :)
From what I know, attributes do not act like regular classes. They are not created when creating the object to which they are applied, and not to one static instance, and not 1 for each instance of the object. They also do not access the class to which they apply.
Instead, they act as properties (attributes ?: P) of the class. Unlike the properties of the .NET class, more similar to the properties of the "one glass property - transparency" property. You can check which attributes apply to the class from reflection, and then act accordingly. This is, in essence, metadata that is tied to a class definition, and not to objects of this type.
You can try to get a list of attributes for a class, method, property, etc. etc. When you get a list of these attributes, this is where they will be created. You can then manipulate the data in these attributes.
eg. Linq tables, properties have attributes on them that determine which table / column they belong to. But these classes do not use these attributes. Instead, the DataContext checks the attributes of these objects when it converts linq expression trees to SQL code.
Now for some real-world examples .. I used them in LinqPad , so don't worry about weird dump (). I replaced it with Console.WriteLine to make the code more understandable for people who do not know about it :)
void Main() { Console.WriteLine("before class constructor"); var test = new TestClass(); Console.WriteLine("after class constructor"); var attrs = Attribute.GetCustomAttributes(test.GetType()).Dump(); foreach(var attr in attrs) if (attr is TestClassAttribute) Console.WriteLine(attr.ToString()); } public class TestClassAttribute : Attribute { public TestClassAttribute() { DefaultDescription = "hello"; Console.WriteLine("I am here. I'm the attribute constructor!"); } public String CustomDescription {get;set;} public String DefaultDescription{get;set;} public override String ToString() { return String.Format("Custom: {0}; Default: {1}", CustomDescription, DefaultDescription); } } [Serializable] [TestClass(CustomDescription="custm")] public class TestClass { public int Foo {get;set;} }
The console result of this method:
before class constructor after class constructor I am here. I'm the attribute constructor! Custom: custm; Default: hello
And Attribute.GetCustomAttributes(test.GetType()) returns this array: (the table shows all the available columns for all records. So, no, the Serializable attribute does not have these properties :)) 
Do you have more questions? Feel free to ask!
UPD: I saw how you asked the question: why use it? As an example, I will tell you about the XML-RPC.NET library. You create your XML-RPC service class using methods that will represent the xml-rpc methods. The main thing now: in XmlRpc, method names can have some special characters, such as periods. This way you can use the flexlabs.ProcessTask () xml rpc method.
You define this class as follows:
[XmlRpcMethod("flexlabs.ProcessTask")] public int ProcessTask_MyCustomName_BecauseILikeIt();
This allows me to name the method the way I like it, but it still uses the public name, as it should.