Light metaprogramming scenarios
Attribute driven helpers: display labels, required validation, mini constructor activator, and CSV like serialization with clear safety tips.
Light metaprogramming scenarios is a free C# Academy lesson on CoddyKit. This is lesson 3 of 3. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, and your progress syncs across the web and the CoddyKit app. The C# Academy course includes 3 lessons in total.
Plan & scope
Aim: Drive tiny behaviors with attributes.
- Map labels
- Validate required fields
- Mini DI activator
- Serialize to CSV like text
Attribute labels
Decorate properties with a DisplayName and reflect it to print friendly labels.
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public sealed class DisplayNameAttribute : Attribute
{
public string Text { get; private set; }
public DisplayNameAttribute(string text) { Text = text; }
}
public sealed class User
{
[DisplayName("User Id")]
public int Id { get; set; }
[DisplayName("Full Name")]
public string Name { get; set; }
}
public class Program
{
static void PrintWithLabels(object o)
{
if (o == null) throw new ArgumentNullException("o");
Type t = o.GetType();
foreach (PropertyInfo p in t.GetProperties())
{
object[] at = p.GetCustomAttributes(typeof(DisplayNameAttribute), false);
string label = at.Length > 0 ? ((DisplayNameAttribute)at[0]).Text : p.Name;
object val = p.GetValue(o, null);
Console.WriteLine(label + ": " + (val == null ? "null" : val.ToString()));
}
}
public static void Main(string[] args)
{
User u = new User { Id = 1, Name = "Ada" };
PrintWithLabels(u);
}
}
Attribute validation
Mark properties with Required; a tiny validator checks and reports the first missing field.
using System;
using System.Reflection;
[AttributeUsage(AttributeTargets.Property)]
public sealed class RequiredAttribute : Attribute { }
public sealed class Product
{
[Required] public string Name { get; set; }
public decimal Price { get; set; }
}
public class Program
{
static bool Validate(object obj, out string error)
{
error = null;
if (obj == null) { error = "object is null"; return false; }
Type t = obj.GetType();
foreach (PropertyInfo p in t.GetProperties())
{
object[] req = p.GetCustomAttributes(typeof(RequiredAttribute), false);
if (req.Length > 0)
{
object v = p.GetValue(obj, null);
if (v == null || (v is string && string.IsNullOrEmpty((string)v)))
{
error = "Required property missing: " + p.Name;
return false;
}
}
}
return true;
}
public static void Main(string[] args)
{
Product ok = new Product { Name = "Mouse", Price = 10m };
Product bad = new Product { Name = null, Price = 5m };
string e;
Console.WriteLine(Validate(ok, out e) ? "OK" : e);
Console.WriteLine(Validate(bad, out e) ? "OK" : e);
}
}
Mini activator
A tiny activator maps a name to each constructor parameter. Provide defaults when missing.
using System;
using System.Reflection;
using System.Collections.Generic;
public sealed class Repo { public string Name; public Repo(string name){ Name = name; } }
public sealed class Service
{
public Repo R; public int Timeout;
public Service(Repo repo, int timeout){ R = repo; Timeout = timeout; }
}
public class Program
{
static object CreateWithArgs(Type t, IDictionary<string, object> args)
{
if (t == null) throw new ArgumentNullException("t");
if (args == null) args = new Dictionary<string, object>();
ConstructorInfo[] ctors = t.GetConstructors();
if (ctors.Length == 0) throw new InvalidOperationException("No public constructor");
ConstructorInfo ci = ctors[0];
ParameterInfo[] ps = ci.GetParameters();
object[] argv = new object[ps.Length];
for (int i = 0; i < ps.Length; i++)
{
object val;
if (args.TryGetValue(ps[i].Name, out val)) argv[i] = val;
else if (ps[i].ParameterType.IsValueType) argv[i] = Activator.CreateInstance(ps[i].ParameterType);
else argv[i] = null;
}
return ci.Invoke(argv);
}
public static void Main(string[] args)
{
IDictionary<string, object> map = new Dictionary<string, object>();
map["repo"] = new Repo("Main");
map["timeout"] = 30;
Service s = (Service)CreateWithArgs(typeof(Service), map);
Console.WriteLine(s.R.Name + " / " + s.Timeout);
}
}
CSV by attributes
Order columns with CsvOrder and escape commas and quotes correctly.
using System;
using System.Reflection;
using System.Text;
using System.Globalization;
[AttributeUsage(AttributeTargets.Property)]
public sealed class CsvOrderAttribute : Attribute
{
public int Index { get; private set; }
public CsvOrderAttribute(int index){ Index = index; }
}
public sealed class Row
{
[CsvOrder(0)] public int Id { get; set; }
[CsvOrder(1)] public string Name { get; set; }
[CsvOrder(2)] public decimal Price { get; set; }
}
public class Program
{
static int GetIndex(PropertyInfo p)
{
object[] at = p.GetCustomAttributes(typeof(CsvOrderAttribute), false);
return at.Length > 0 ? ((CsvOrderAttribute)at[0]).Index : Int32.MaxValue;
}
static string ToCsv(object o)
{
if (o == null) throw new ArgumentNullException("o");
Type t = o.GetType();
PropertyInfo[] props = t.GetProperties();
Array.Sort(props, delegate(PropertyInfo a, PropertyInfo b)
{
int ia = GetIndex(a), ib = GetIndex(b);
return ia.CompareTo(ib);
});
StringBuilder sb = new StringBuilder();
for (int i = 0; i < props.Length; i++)
{
object v = props[i].GetValue(o, null);
string cell = v == null ? "" : Convert.ToString(v, CultureInfo.InvariantCulture);
if (i > 0) sb.Append(",");
sb.Append(cell);
}
return sb.ToString();
}
public static void Main(string[] args)
{
Row r = new Row { Id = 7, Name = "Cable, HDMI", Price = 12.5m };
Console.WriteLine(ToCsv(r));
}
}
Tips & safety
Tips:
- Prefer typeof and cached metadata in hot paths.
- Validate members before GetValue or Invoke.
- Keep helpers small and unit tested.
- Fail fast with helpful messages.
Reflection guideline
Recap
Recap: Attributes can drive tiny mappers, validators, activators, and serializers. Keep reflection minimal and safe.
Learn C# with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 93
- Lessons
- 346
Frequently Asked Questions
Is the “Light metaprogramming scenarios” lesson free?
Yes — the full text of “Light metaprogramming scenarios” is free to read here on the web. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO. The C# Academy course includes 3 lessons in total.
What will I learn in “Light metaprogramming scenarios”?
Attribute driven helpers: display labels, required validation, mini constructor activator, and CSV like serialization with clear safety tips. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start C# Academy?
No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 3.
How long does the “Light metaprogramming scenarios” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this C# Academy lesson?
Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Type, MethodInfo, activation, custom attributes
- Source-level info (Caller attributes)
- Light metaprogramming scenarios