I am pretty sure that everyone already used a micro-class in code. Everytime you use a static class that contains only constants, you are using a micro-class (for example ResourceKeys or Roles).

1
2
3
4
5
6
public static class Roles
{
public const string Administrator = "Administrator";
public const string PowerUser = "PowerUser";
public const string User = "User";
}

What is a micro-class?

A micro-class is a basically a wrapper class around a built-in Type (or Types). It could have overridden equality members, GetHashCode and/or have implicit operators.

1
2
3
4
5
6
7
8
9
public class MicroClass
{
public MicroClass(string value)
{
Value = value;
}

public string Value { getprivate set; }
}

Is “property” just any “built-in type”?

Any time you are using a built-in Type, you should be asking yourself is {property} just any {built-in Type}.

  • Path: Is Path just any string?
  • BirthDate: Is BirthDate just any nullable DateTime?
  • Amount: Is amount just any double?
  • Angle: Is Angle just any double?
  • Email: Is Email just any string?

When the answer to the question is ‘no’, then the property is a good candidate for a micro-class.

Examples

Horoscope

When thinking about a Horoscope sign, one could just say it’s a string holding the name of the sign:

1
2
3
4
public static class Horoscope
{
public const string Aquarius = "Aquarius";
}

Or we could use an Enum to define possible values:

1
2
3
4
public enum HoroscopeEnum
{
Aquarius = 0
}

Or we could use a micro-class for it:

1
2
3
4
5
6
7
8
9
10
11
public class Horoscope
{
public static readonly Horoscope Aquarius = new Horoscope("Aquarius");

private Horoscope(string name)
{
Name = name;s
}

public string Name { get; private set}
}

Although the micro-class needs a little more code, it has the advantage when it comes to adding behavior and properly encapsulation that behavior. As shown here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class Horoscope
{
public static readonly Horoscope Aquarius = new Horoscope("Aquarius"new DateTime(1753120), new DateTime(1753218));
public static readonly Horoscope Pisces = new Horoscope("Pisces"new DateTime(1753219), new DateTime(1753320));
public static readonly Horoscope Aries = new Horoscope("Aries"new DateTime(1753321), new DateTime(1753419));
public static readonly Horoscope Taurus = new Horoscope("Taurus"new DateTime(1753420), new DateTime(1753520));
public static readonly Horoscope Gemini = new Horoscope("Gemini"new DateTime(1753521), new DateTime(1753621));
public static readonly Horoscope Cancer = new Horoscope("Cancer"new DateTime(1753622), new DateTime(1753722));
public static readonly Horoscope Leo = new Horoscope("Leo"new DateTime(1753723), new DateTime(1753822));
public static readonly Horoscope Virgo = new Horoscope("Virgo"new DateTime(1753823), new DateTime(1753922));
public static readonly Horoscope Libra = new Horoscope("Libra"new DateTime(1753923), new DateTime(17531023));
public static readonly Horoscope Scorpio = new Horoscope("Scorpio"new DateTime(17531024), new DateTime(17531121));
public static readonly Horoscope Sagittarius = new Horoscope("Sagittarius"new DateTime(17531122), new DateTime(17531221));
public static readonly Horoscope Capricorn = new Horoscope("Capricorn"new DateTime(17531222), new DateTime(1753119));

private Horoscope(string name, DateTime minDate, DateTime maxDate)
{
Name = name;
MinimumDate = minDate;
MaximumDate = maxDate;
}

public string Name { getprivate set; }
public DateTime MinimumDate { getprivate set; }
public DateTime MaximumDate { getprivate set; }

public static IEnumerable All
{
get
{
yield return Aquarius;
yield return Pisces;
yield return Aries;
yield return Taurus;
yield return Gemini;
yield return Cancer;
yield return Leo;
yield return Virgo;
yield return Libra;
yield return Scorpio;
yield return Sagittarius;
yield return Capricorn;
}
}

public bool Matches(DateTime date)
{
var month = date.Month;
var day = date.Day;

if (month == 2 && day == 29) day--;

var bDate = new DateTime(1753, month, day);

return MinimumDate <= bDate && bDate <= MaximumDate;
}

public static Horoscope For(DateTime date)
{
return All.Single(x => x.Matches(date));
}

public override string ToString()
{
return Name;
}
}

BirthDate

With a birthdate we are usually not interested in hours, minutes or seconds, we just want to store the date of birth:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BirthDate
{
private readonly DateTime? birthDay;

public BirthDate(DateTime? birthday)
{
birthDay = birthday.HasValue ? birthday.Value.Date : birthday;
}

public BirthDate(int year, int month, int day)
{
birthDay = new DateTime(year, month, day);
}

public DateTime? BirthDay
{
get { return birthDay; }
}

public override string ToString()
{
return string.Format("{0:dd/MM}", birthDay);
}
}

Calculating the age becomes as easy as this:

1
2
3
4
5
6
7
8
public int CalculateAge(DateTime date)
{
date = date.Date;
if (!BirthDay.HasValue || BirthDay.Value > date) return 0;
var age = date.Year - BirthDay.Value.Year;
if (date < BirthDay.Value.AddYears(age)) age--;
return age;
}

Now with reusing the horoscope micro-class, we can add that behavior to birthdate:

1
2
3
4
public Horoscope GetHoroscope()
{
return !birthDay.HasValue ? null : Horoscope.For(birthDay.Value);
}

Angle

How about an angle micro-class, that has by default a degrees, minutes, seconds ToString() representation and normalizes negative angles etc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Angle
{
public Angle()
{
}

public Angle(double value)
{
this.Value = Normalize(value);
}

public double Value { getprivate set; }

public int Degrees
{
get { return (int) Math.Floor(ToTimeSpan().TotalHours); }
}

public int Minutes
{
get { return ToTimeSpan().Minutes; }
}

public int Seconds
{
get { return ToTimeSpan().Seconds; }
}

private TimeSpan ToTimeSpan()
{
return TimeSpan.FromHours(Math.Abs(Value));
}

private double Normalize(double angle)
{
if (angle == 0.0return 0.0;
var rest = Math.Abs(angle)%360;
var sign = Math.Sign(angle);
return sign == -1 ? rest\*sign + 360.0 : rest;
}

public override string ToString()
{
return string.Format("{0}° {1}' {2}\"", Degrees, Minutes, Seconds);
}
}