293. Generic constraint hack

Feature מסוים שאין לנו תמיכה בו בC# הוא לשים Constraintים על פרמטרים גנריים כך שירשו מהטיפוסים Array, Enum, Delegate או ValueType.

(ראו גם טיפ מספר 29)

תכלס, בשביל שני האחרונים אפשר להסתדר: במקום ValueType, אפשר לכתוב

1
where T : struct

(ראו גם טיפ מספר 31)

ובמקום Array, אפשר לדאוג שהפונקציה תקבל T[] (או אולי T[,] או T[,,] וכו’, תלוי במימד של המערך)

עם זאת, השניים הראשונים לפעמים מתבקשים. עשינו בעבר מספר טריקים, למשל שמנו Constraint ש

1
where T : IConvertible

כדי לצמצם את מספר האפשרויות שישלחו טיפוס שהוא לא Enum.

(ראו גם טיפים מספר 72, 209)

מסתבר שבאופן כללי, הCLR דווקא כן תומך בFeature הזה. אם תקחו קוד IL עם Constraint רגיל, ותשנו את הIL שהטיפוס שממנו יורש T הוא Enum, זה דווקא יעבוד לכם! (כלומר, אם תוסיפו Reference לDLL הזה, באמת תהיה לכם את האכיפה)

מישהו מצא באינטרנט Hack מתוחכם בשפה המאפשר להשתמש בConstraint הזה בכל זאת:

השימוש הוא באמצעות Naked Constraints (ראו גם טיפ מספר 34) ונראה ככה:

1
2
3
4
5
public abstract class BaseTempClass<TTemp>
{
public abstract TEnum[] GetValues<TEnum>()
where TEnum : TTemp;
}

כעת אפשר לעשות משהו כזה:

1
2
3
4
5
6
7
public class EnumProvider : BaseTempClass<Enum>
{
public override TEnum[] GetValues<TEnum>()
{
return (TEnum[]) Enum.GetValues(typeof (TEnum));
}
}

ככה תהיה ואלידיאציה בזמן קימפול:

1
2
EnumProvider provider = new EnumProvider();
BindingFlags[] flags = provider.GetValues<BindingFlags>();

הקוד הבא עם זאת, לא יתקמפל:

1
2
EnumProvider provider = new EnumProvider();
int[] flags = provider.GetValues<int>();

הדבר הזה אמור לעבוד גם על Delegateים, אבל לא ניסיתי את זה.

בכל מקרה זה בעיקר מגניב, אבל לא הייתי ממליץ לאף אחד ליצור מחלקת ביניים בשביל הFeature הזה.

(אלא אם כן יש לכם סיבה ממש ממש ממש טובה, וכנראה אין לכם)

שימו לב שזה לא מאפשר לנו לעשות Extension Methods לEnum, כי אנחנו צריכים פה ירושה, ואין ירושה במחלקות סטטיות. גם אם הייתה, זה היה בלתי אפשרי, מאחר ולא ניתן להגדיר Extension Methods במחלקות גנריות סטטיות.

הפתרון הזה נמצא ע"י אחד המנהלים מStackOverflow עם הכינוי SLaks

המשך יום מלא Enumים בטוחים טוב!

שתף