A coworker pointed me towards an interesting blog post by John Skeet about the changed behavior of static constructors in C# 4.0 (yes, I know, it’s been a few years now, but I never ran into it).
It seems that C# 4.0 now tries to be lazier when instantiated static fields. So if I have a side-effect free static method that doesn’t touch any members, calling it will not bother initializing the static fields. From Skeet’s post:
- class Lazy
- {
- private static int x = Log();
- private static int Log()
- {
- Console.WriteLine("Type initialized");
- return 0;
- }
- public static void StaticMethod()
- {
- Console.WriteLine("In static method");
- }
- }
Calling StaticMethod() will print “In static method”, but will not print “Type Initialized”!
This was a bit worrying. John Skeet said it shouldn’t impact existing code, but we were not so sure. Imagine a class whose static constructor does some unmanaged initialization work (creates a Performance Counter, for instance) while the static method writes to the counter. This could potentially cause a hard to find exception, since our expectation was that static constructors can be relied upon to always be called first.
So we ran a quick test (and by “we” I most mean Amir), and it seems that this behavior isn’t as problematic as we thought. Look at this code, adding a static constructor to the class above:
- class Lazy
- {
- private static int x = Log();
- private static int Log()
- {
- Console.WriteLine("Type initialized");
- return 0;
- }
-
- static Lazy()
- {
- Console.WriteLine("In static constructor");
- }
-
- public static void StaticMethod()
- {
- Console.WriteLine("In static method");
- }
- }
The only difference is that we have an explicit static constructor, rather than the implicit one that initializes the field. Unlike the first test case, in this case calling StaticMethod() did call the static constructor, and we could see “In static constructor” printed before “In static method”. The compiler is smart enough to see that we have an explicit constructor defined, so that means we want it to be called, and it will be called. This was reassuring.
But wait, there’s more! It seems that once type initialization was triggered by the presence of the explicit static constructor, it went all the way. Even the x parameter was initialized, the Log() method was called, and “Type Initialized” was printed to the console, even before the static constructor. This was the behavior I was used to, where static field initializations are added to the beginning of the .cctor.
To summarize, the new lazy type initialization behavior for C# 4.0 is interesting, since it allows static classes that contain only side-effect free methods (for instance, classes containing popular Extension Methods) to avoid expensive and unnecessary initializations. But it was designed smartly enough to recognize when initialization is explicitly desired, and be a bit less lazy in that case.
(And thanks again to Igal Tabachnik and Amir Zuker)
עברו כמה שנים מאז שעבדתי עם Visual Basic.NET, והייתי בטוח שבימינו, ב-2013, נגמרו כבר הויכוחים של “איזו שפה יותר טובה”. שתיהן שפות עם יכולות דומות, והבחירה ביניהן היא בעיקר העדפה סגנונית. אבל מידי פעם אני עדיין רואה בפורומים או ב-Stack Overflow שאלה בנוגע ליתרונות של שפה אחת על השניה, וספציפית על יכולות ה-COM Interop של VB. האם היא באמת שפה נוחה יותר?
המצב טרום .NET 4.0
לפני .NET 4.0 (שיצאה, יש להזכיר, לפני כמעט 3 שנים), התשובה היתה “כן”, אם כי “כן” קצת דחוק. VB הגיעה מתוך עולם ה-Late Binding, ולא היה לה בעיה להעביר לרכיב רק חלק מהפרמטרים שהם צריכים, ולדאוג לשאר לברירות מחדל מאחורי הקלעים. היתרון הזה היה בולט במיוחד כשעובדים מול אובייקטים של אופיס, שמכילים מתודות שמקבלות חמישה, עשרה, ולפעמים שישה-עשר פרמטרים אופציונאליים, כשברוב המקרים אנחנו נרצה להעביר רק אחד או שניים מהם, אם בכלל. ההבדל בין C# ל-VB במקרה כזה הוא ההבדל בין הקוד הזה ב-C#:
Code Snippet
- myWordDocument.SaveAs(ref fileName, ref missingValue,
- ref missingValue, ref missingValue,
- ref missingValue, ref missingValue,
- ref missingValue, ref missingValue,
- ref missingValue, ref missingValue,
- ref missingValue, ref missingValue,
- ref missingValue, ref missingValue,
- ref missingValue, ref missingValue);
לבין זה ב-VB:
Code Snippet
- MyWordDocument.SaveAs(FileName: fileName)
שזה, אתם חייבים להודות, הבדל לא קטן.
אבל מצד שני, לא כל ממשק COM עובד ככה. למען האמת, בכל שנותי אני חושב שנתקלתי בסגנון כתיבה הזה רק בספריות של אופיס, ולא בשום רכיב אחר, בין אם של מיקרוסופט (כמו רכיבי DirectShow שאיתם עבדתי לאחרונה) או של חברות אחרות. ברוב המקרים, לעשות COM Interop ב-C# פשוט באותה מידה כמו ב-VB.
.NET 4.0 – גם C# זוכה ליחס
היתרון הקטן הזה של VB נהיה גם הוא פחות רלבנטי החל מ-C# 4.0 ו-Visual Studio 2010, משתי סיבות:
הראשונה היא מילת הקסם שהתווספה לה לשפה, dynamic. כשאנחנו מגדירים אובייקט כדינאמי, אנחנו מוותרים על הרבה מה-static type checking שהקומפיילר עושה בשבילנו, ונותנים למנגנון ה-late binding (שסופח ל-C# באמצעות ה-DLR, ה-Dynamic Language Runtime שנבנה לתמוך בשפות דינאמיות כמו Python או Ruby) לעשות בשבילנו את העבודה ב-runtime. מכיוון שיש late binding, אפשר להתעלם מהפרמטרים המיותרים ולתת ל-DLR להבין לבד מה לחבר לאיפה. אפשר למצוא הסבר כללי על שימוש ב-dynamic במאמר הזה, והסבר יותר ספציפי על השימוש בו ל-COM Interop במאמר הזה.
אבל אם אנחנו שקלנו לעבור ל-VB רק בשביל לעבוד מול רכיבי אופיס, אז C# 4.0 מאפשר לנו להקל על החיים גם בלי לצלול לעולם העכור של dynamic languages. אנחנו עדיין לא יכולים להתעלם מ-ref parameters במתודות של מחלקות, אבל אנחנו כן יכולים להתעלם מהפרמטרים הללו בקריאות למתודות על interfaces. אני לא יכול להגיד שאני מבין לגמרי למה זה ככה (טרם התעמקתי) אבל זה אומר שאם מה שיש לנו ביד זה לא אובייקט DocumentClass של אופיס, אלא interface מסוג Document, אז אפשר לקרוא למתודות שעליו כמו ב-VB.
לנוחותינו, ספריות ה-Interop של אופיס חושפות לנו את ה-interfaces האלה, דרך property בשם InnerObject שיש על רוב האובייקטים המעניינים. (כן, זה property בשם InnerObject שחושף את ה-Interface. אני יודע. זה מבלבל. זה מה יש). אז את הקוד שכתבנו למעלה אפשר להחליף, ב-C# 4.0, בזה:
Code Snippet
- myWordDocument.InnerObject.SaveAs(FileName: fileName);
וזהו! אמנם לא כל אובייקט של אופיס זוכה ל-InnerObject הזה, אבל המרכזיים והגדולים (שהם גם אלה עם המתודות המפלצתיות) דווקא כן. ואם אין ברירה, נעבוד גם עם dynamic – או עם Missing.Value.
אז מתי בכל זאת VB?
כל המלל לעיל לא נועד לשכנע מישהו לבחור דווקא ב-C# או דווקא לא ב-VB. שתי השפות יכולות לעשות פחות או יותר כל אחת את מה שהשניה יכולה לעשות, והבחירה בה צריכה להיות של העדפה אישית לסגנון כתיבה, לא בגלל יכולת קריטיות לכאן או לכאן. זה מה שניסיתי להעביר כאן – כמו ש-VB יישרה קו עם C# לאורך השנים בכמה פיצ’רים שהיו חסרים, כך גם C# השלימה את החסר לה בנוגע לעבודה עם אופיס. הבחירה היא, שוב, סגנונית בלבד.
Remote Debugging in Visual Studio is a wonderful feature, especially during the later stages of testing and deployment, and even (if all else fails) when in production, but getting it to work is rarely smooth. Everything is fine if both the computer running VS and the one running the application are in the same domain, but when they aren’t, things start to break, fast. I
So for those stuck debugging remote applications in different domains, here’s a quick guide to east the pain.
- On the the remote machine, install the Visual Studio Remote Debugging package, which comes with the version of Visual Studio you’re using.
- On the remote machine, create a new local user account. Give it the exact same name and password that you use on your development machine.
If you’re developing when logged into MYCORPDOMAIN\MyUserName, with password MYPASS123, you wil have to create a local user REMOTEMACHINE\MyUserName with the same password. It doesn’t matter that the domains are different and there’s no trust relationship and so forth. Just have them be the same username and password. - Give REMOTEMACHINE\MyUserName permissions. Administrator permissions is the safest, though you should be able to get it to work with a more restricted group, but I haven’t checked it yet.
- Run the application you want to debug using the MyUserName credentials. In Windows 7, this means using Shift-Rightclick and choosing Run As Different User. For Vista, there’s a shell extension to enable it.
- Run the Remote Debugging program, again under MyUserName credentials, same as above. The Remote Debugger will start with a new session named MyUserName@REMOTEMACHINE.
- Copy the remote debugging session name from the remote machine (you can copy it via the Tools –> Options menu).
- In your development machine, open Visual Studio, and go to Debug –> Attach to Process.
- In the Attack to Process screen, paste the remote debugging session name into the Qualifier textbox (the second one from the top).
- Voila! You are now debugging remotely!
Working with ESRI’s ArcGIS package, especially the WPF API, can be confusing. There’s the REST API, the SOAP APIs, and the WPF classes themselves, which expose some web service calls and information, but not everything. With all that, it can be hard to find specific features between the different options. Some functionality is handed to you on a silver platter, while some is maddeningly hard to implement.
Today, for instance, I was working on adding a Legend control to my map-based WPF application, to explain the different symbols that can appear on the map.
This is how the legend looks on ESRI’s own map-editing tools:

but this is how it looks when I used the Legend control, supplied out of the box by ESRI:

Very pretty, but unfortunately missing the option to display the name of the fields that make up the symbology.
Luckily, the WPF controls have a lot of templating/extensibility points, to allow you to specify the layout of each field:
1: <esri:Legend>
2: <esri:Legend.MapLayerTemplate>
3: <DataTemplate>
4: <TextBlock Text="{Binding Layer.ID}"/>
5: </DataTemplate>
6: </esri:Legend.MapLayerTemplate>
7: </esri:Legend>
but that only replicates the same built in behavior. I could now add any additional fields I liked, but unfortunately, I couldn’t find them as part of the Layer, GraphicsLayer or FeatureLayer definitions. This is the part where ESRI’s lack of organization is noticeable, since I can see this data easily when accessing the ArcGis Server’s web-interface, but I had no idea how to find it as part of the built-in class. Is it a part of Layer? Of LayerInfo? Of the LayerDefinition class that exists only in the SOAP service?
As it turns out, neither. Since these fields are used by the symbol renderer to determine which symbol to draw, they’re actually a part of the layer’s Renderer. Since I already had a MyFeatureLayer class derived from FeatureLayer that added extra functionality, I could just add this property to it:
1: public string LegendFields
2: {
3: get
4: {
5: if (this.Renderer is UniqueValueRenderer)
6: {
7: return (this.Renderer as UniqueValueRenderer).Field;
8: }
9: else if (this.Renderer is UniqueValueMultipleFieldsRenderer)
10: {
11: var renderer = this.Renderer as UniqueValueMultipleFieldsRenderer;
12: return string.Join(renderer.FieldDelimiter, renderer.Fields);
13: }
14: else return null;
15: }
For my scenario, all of my layers used symbology derived from a single field or, as in the examples above, from several of them. The renderer even kindly supplied me with the comma to separate the fields with. Now it was a simple matter to get the Legend control in line – assuming that it was bound to a collection of MyFeatureLayer:
1: <esri:Legend>
2: <esri:Legend.MapLayerTemplate>
3: <DataTemplate>
4: <StackPanel>
5: <TextBlock Text="{Binding Layer.ID}"/>
6: <TextBlock Text="{Binding Layer.LegendFields}" Margin="10,0,0,0" TextStyle="Italic"/>
7: </StackPanel>
8: </DataTemplate>
9: </esri:Legend.MapLayerTemplate>
10: </esri:Legend>
and get the look I wanted – the list of fields below the layer name, indented.
“NUnit is being problematic again”, they told me when I came to visit the project. “When running unattended it’s not catching assertions properly and the test is coming up green, but when stepping through in the debugger, it works fine.”. It’s nice, when getting a passing test is acknowledged as a bad thing, at least when you don’t expect it to be. In this case, though, the fault wasn’t really with NUnit.
1: [Test]
2: [ExpectedException]
3: public void DoTheTest()
4: {
5: _myComponent.RunMethod();
6: Assert.IsFalse(_myComponent.EverythingIsFine);
7: }
“It’s simple. Either the method throws an exception, or at the very least – the EverythingIsFine property won’t be set to “True”, so the assert will catch the problem. But in their case, no exception was thrown and Everything wasn’t Fine, but the Assert call wasn’t raising a red flag – unless they stepped through, in which case it did. What’s going on?
The basic problem is that to many developers, NUnit is a kind of magic. You write a self-contained little bit of code, the [Test] method, but you don’t call it yourself, you don’t get a feel for the whole execution flow. The result – developers don’t exercise the same sort of judgement they do on their own application code.
The root of the problem here is that the [ExpectedException] attribute told NUnit to pass the test if an exception is thrown. NUnit’s Assertion utilities, however, use exceptions as the mechanism for failing tests – when an assertion is hit, it raises an exception – it can be an AssertionException. For various mock frameworks, it can be an ExpectationException. It doesn’t matter – it’s these exceptions that make the test fail, and not some behind-the-scenes magic. Because the test had an open-ended [ExpectedException] attribute, these exceptions were caught, fulfilling the condition, and NUnit was happy.
What can we do to avoid this?
- Be explicit. Don’t try to catch ALL exceptions with [ExpectedException]. If you’re expecting an exception, you’re probably expecting a specific exception. Specify it.
- Be aware of how your tools work. If NUnit works by throwing an exception, don’t wrap it with a try/catch. Your tests are C# code too, as is the plumbing to enable it. It plays by the same rules.
<Placeholder for StackOverflow Weekday counter>
In my current project we’re using Moq as our mocking framework for unit testing. It’s a very nice package – very simple, very intuitive for someone who has his wrapped around basic mocking concepts, but tonight I ran into an annoying limitation that, while rare, impeded my tests considerably.
Consider the following code:
1: public interface IOut
2: {
3: void DoOut (out string outval);
4: }
5:
6: [Test]
7: public void TestMethod()
8: {
9: var mock = new Mock<IOut>();
10: string outVal = "";
11: mock.Setup(out => out.DoOut(out outVal))
12: .Callback<string>(theValue => DoSomethingWithTheValue(theValue));
13: }
What this code SHOULD do is create a mock object for the IOut interface that, when called with an out parameter, both assigns a value to it, and does something with the value in the Callback.
Unfortunately, this does not work.
The reason it doesn’t work is that the Callback method has many overloads, but they are all to various variants of the Action<> delegate. Either Action<T1>, which receives one parameter, or to Action<T1, T2, T3>, which receives three. None of these support out parameters. But none of these delegates has a signature with an out parameter. The result, at runtime, would be an error to the effect of
“Invalid callback parameters <string> on object ISetup<string&>”
Note the highlighted bits – The Setup method referred to a string& (a ref/out param), while the Callback inferred an Action<string> delegate, which expectes a regular string param.
So what CAN we do?
The first option is submit a patch to the Moq project. It’s open-source, and a solution might be appreciated, especially since the project’s lead, Daniel Cazzulino, pretty much acknowledged that this is not supported right now. I might do that later, but right now it’s the middle of the night and I just want my tests to pass. So I manage to hack together this workaround which does the trick:
1: public static class MoqExtension
2: {
3: public delegate void OutAction<TOut> (out TOut outVal);
4: public static IReturnsThrows<TMock, TReturn>
5: OutCallback<TOut, TMock, TReturn>
6: (this ICallback<TMock,T4> mock,
7: OutAction<TOut> action)
8: where TMock : class
9: {
10: mock.GetType()
11: .Assembly.GetType("Moq.MethodCall")
12: .InvokeMember("SetCallbackWithArguments",
13: BindingFlags.InvokeMethod
14: | BindingFlags.NonPublic
15: | BindingFlags.Instance,
16: null, mock, new object[] {action});
17: return mock as IReturnsThrows<TMock,TReturn>;
18: }
19: }
We’ve created a new delegate, OutAction<T>, that has out T as its parameters, and we’ve added a new extension method to Moq, OutCallback, which we will use instead of Callback. What this extension method does is use brute-force Reflection to hack into Moq’s internals and call the SetCallbackWithArguments method, which registers the Callback with Moq. Yes, it even works. I’m as surprised as you are.
Note and Caveats:
1) This code works for the very specific void Function (out T) signature. You will have to tailor it to the specific methods you want to mock. To make it generic, we’ll have to create a bucket-load of OutAction overloads, with different combinations of out and regular parameters.
2) This is very hacky and very fragile. If Moq’s inner implementation changes in a future version (the one I’m using is 4.0.10827.0), it will break.
3) I don’t like using out params, usually, but this is a 3rd party library, so I have to work with what I got.
Comments? Questions? Scathing criticisms on how ugly my code is? A different, simpler method of accomplishing this that I totally missed? Bring’em on.
When developing WPF applications,
Snoop is a wonderful tool that can let us see our visual tree at runtime, and find errors in data binding that are otherwise hard to track down in debugging. However, Snoop has an annoying limitation – it can’t show us data-bound Attached Properties. Let’s say we have the following XAML:
<Canvas>
<Button Canvas.Left="{Binding Location.X}"
Canvas.Top="{Binding Location.Y"}/>
</Canvas>
Snooping this application won’t show Canvas.Left and Canvas.Top among the Button’s properties, since they’re not a part of the Button object. If we have a bug in our binding, we won’t be able to find it.
So what do we do? We can set various TraceLevel attributes to save the binding errors to a log, but a quick workaround during debugging is to bind our data, in addition to the attached properties, to the element’s Tag property, a generic object that can bind to anything, and allow us to Snoop the binding – and find out any problems:
<Canvas>
<Button Canvas.Left="{Binding Location.X}"
Canvas.Top="{Binding Location.Y}"
Tag="{Binding Location}" />
</Canvas>
This allowed me to find out, quickly, that my bug was simple – I had forgotten to make my Location property public, causing the binding to fail silently.
מי שמסתובב בת”א בחודשים האחרונים, בטח ראה את עמדות האופניים הירוקות שהתחילו לצוץ באזור
המרכז, ולאחרונה גם התחילו לתפקד ולהשכיר אופניים לעוברים ושבים. היוזמה מבורכת, והתחלתי כבר להשתמש בה, אבל חייו של רוכב האופניים התל-אביב יכולים להיות טובים עוד יותר עם קצת עזרים טכנולוגיים. הנה שני רעיונות שהתחלתי לעבוד עליהם כדי לאפשר למשתמש הסמארטפונים, בעיקר, למצוא בקלות עמדות אופניים בעודם בדרכים:
1. גירסת המפה. שירות Google Maps מאפשר למשתמשים ליצור מפות מותאמות אישית, והלביש את האתרים שבהן על גבי המפה. יצרתי מפה כזו המכילה את כל עמדות האופניים הפעילות כרגע בת”א, ופתחתי אותה כך שכל אחד יוכל להוסיף אותו ל-My Maps שלו. בשלב הזה, אפשר ללכת לאפליקציית Google Maps על מכשירי אנדרואיד וסימביאן (לא ידועה לי על דרך באייפון) ולהציג את העמדות על גבי המפה בטלפון, כמו שאפשר לראות כאן משמאל.
את המפה אפשר למצוא כאן. פירוט נוסף אפשר לראות כאן.
2. למי שלא מספיקה לו המפה הסטטית, התחלתי לעבוד על אפליקציה חכמה יותר, שיודעת גם להחזיר את התחנה הקרובה ביותר ומידע עליהן. השלב הראשון של ה-API הוא רק פונקציית get nearest, המחזירה את התחנה הקרובה ביותר. בשלב הבא, גם החזרת מידע על התחנות, כולל מס’ האופניים ועמדות העגינה הזמינות.
ה-API ממומש כ-web service מבוסס REST. ה-endpoint הוא:
http://www.strawjackal.org/telofun/getnearest?lat=xxx&lng=yyy
והוא מחזיר תוצאות בפורמט JSON:
{"isactive":"1", "id":"368", "address":"\u05d0\u05dc\u05e0\u05d1\u05d9 90 \u05e4\u05d9\u05e0\u05ea \u05de\u05d0\u05d6\u05d4",
"lat":"32.066918651","lng":"34.771511391","distance":
0.45585337300967}
הפרמטרים לשירות הם:
lat – Latitude of current user position to search from, as a floating-point number. ex: 32.070.
lng – Longitude of current user position, as a floating-point number. ex: 34.768.
inactive – (optional) include inactive bike stations. Values can be 1 or 0. Default is 0.
הערכים המוחזרים הם:
id – Unique bike station identifier, as defined by the Tel-O-Fun website (not local).
isactive – Whether the station is currently active.
address – Name/address of station, in Hebrew.
lat/lng – Latitude and Longitude of station.
distance – Distance between station and current position, in kilometers.
השירות זמין לשימוש חופשי, אבל אני אשמח לדעת אם אתם משתמשים בו באפליקציה שלכם, וכמובן, אשמח גם לקרדיט אצלכם באפליקציה.
אני אוהב את הענן. באמת שאני אוהב. אני אוהב את זה שהדואר שלי יושב בג’ימייל וזמין מכל מחשב או טלפון ראוי לשמו. אני אוהב את זה שהמסמכים שלי מסונכרנים עם דרופבוקס, מעודכנים תמיד על שני המחשבים שלי וזמינים גם דרך הדפדפן כשצריך. באמת שאני אוהב את זה. אבל כל הזמן הטכנולוגיה מזכירה לי עד כמה המצב של הענן, ושל אפליקציות ווב בכלל, הוא פרימיטיבי ומגושם יחסית למה שאני רגיל אליו על המחשב האישי עצמו.
אני לא מתכוון אפילו להכנס להתלהבות התקשורתית שיש כל פעם שגוגל מוסיפה פיצ’ר טריביאלי לג’ימייל (חדש! אפשר לגרור קבצים אל תוך הודעה! באמת? דראג-אנד-דרופ זה מה שמלהיב אותנו היום?), אלא למחסור מאד בסיסי ביכולת של הענן לתפקד בלי המחשב האישי שלי. אני יכול לשלוח מייל מהאתר של ג’ימייל או לשמור מסמך באתר של דרופבוקס, אבל אני לא יכול לקשר בין השניים באופן ישיר. אם אני רוצה לצרף למייל מסמך מדרופבוקס, אני צריך לשמור אותו, ידנית, לספריה זמנית במחשב האישי שלי, ואז לצרף אותו חזרה למייל שלי. וזה מטופש. זה ממש ממש מטופש.
ואפילו יותר משזה מטופש, זה גם מיותר. בשביל שג’ימייל יוכל לדבר ישירות עם דרופבוקס, כל מה שצריך זה ששני השירותים יממשו איזה פרוטוקול משותף שיאפשר להם לדבר אחד עם השני. פרוטוקול כזה כבר קיים – WebDAV – אבל יכול להיות שקיימים כבר פרוטוקולים יותר נוחים, פשוטים ויעילים. עכשיו רק צריך שג’ימייל יתמכו בזה, ויאפשרו לי להגדיר, כחלק מהגדרות החשבון שלי, שורה של שרתי מסמכים שאיתם הייתי רוצה לעבוד, ולתת לי לקשר ישירות.
אפשרות נוספת היא לעשות את זה דרך הדפדפן, או אפילו מערכת הפעלה. שחלונית בחירת הקבצים שקופצת כשאני לוחץ Attach תתן לי אפשרות לגשת גם לדרופבוקס שלי. מיקרוסופט כבר עושה דברים דומים עם שרתי SharePoint, אבל התמיכה מוגבלת, בעייתית, ובעיקר לא נותנת מענה לצורך שזה יעבוד מכל מקום, מכל מחשב. אבל זה עדיף על למלא את ספריית ה-Temporary Files שלי עם קבצים מיותרים, זה בטוח.
פיני דיין כותב בבלוג שלו על שני classים נידחים שמתחבאים להם ב-System.Web כדי לשמור צמדים ושלשות של אובייקטים: Pair ו-Triplet. אני חושב ששימוש באובייקטים הללו הוא רעיון רע, ועדיף שיקברו יחד עם ה-.NET Framework 1.0 שהוליד אותם. יש לי שתי סיבות:
א) המחלקות הללו יושבות ב-System.Web.UI, שיושב תחת System.Web.dll. אם אני לא כותב מערכת Web, זה מיותר לחלוטין להוסיף Reference ל-System.Web רק בשבילהם. וזה גם בלי להזכיר שהם לא היו אמורים לשבת ב-Namespace הזה מלכתחילה – אין בהם שום קשר ל-UI. גם אין בקלאסים הללו שום תחכום או לוגיקה שאנחנו לא יכולים לכתוב בעצמנו ב-3 שורות קוד שלא גוררות Reference.
ב) השנה היא 2009, והגרסא היא .NET 3.5, וכבר 4 שנים אין הצדקה לעבוד עם רכיבים מסוג Object ועלויות ה-boxing וה-casting שהם גוררים. אין שום בעיה להגדיר אובייקט Pair<FirstType, SecondType> ולהשתמש ב-Generics בשביל זה. אני אצטרך טיעונים ממש טובים כדי לשקול עיצוב שדורש צמדים לא מוגדרים של אובייקטים לא ידועים.
שפות התכנות שאיתן אנחנו עובדים קשורים באופן הדוק לשפה האנגלית שעליה הן בנויות – לולאות While ותנאי If הם הבסיס של שפות רבות, ורוב המידע שאנחנו מתבססים עליו הוא באנגלית, ממאמרי MSDN לספרים ולהרצאות מוקלטות באינטרנט. לכן זה לא מפתיע שבמקומות רבים – רוב מקומות העבודה שעבדתי בהם – כל ההתעסקות סביב התכנות היתה באנגלית, כולל מסמכי העיצוב והתכנון ואפילו האימיילים הפנימיים בין המתכנתים בענייני הפרוייקט.
הנוהג הזה כל-כך נפוץ שאני מכיר מתכנתים רבים שמתנגדים באופן נחרץ לשילוב של עברית במסמכים הטכניים של המערכת. המטרה שלי כאן היא להעלות כמה סיבות חשובות בעד כתיבת מסמכים בעברית. אפילו מסמכי עיצוב וארכיטקטורה.
נתחיל מזה שאני אדגיש שאני לא מרחיב את ההמלצה הזו לכתיבת קוד בעברית. למרות ש-C# ושפות מודרניות אחרות מאפשרות הגדרת משתנים עם שמות ברוב טווח ה-Unicode, אני חושב שלשלב תווים עבריים בתוך הקוד עצמו זה רע1. גם כי השפות עצמן נכתבות משמאל לימין וגם כי זה גורם לשינוי-הקשר2 חריף לקורא. גם בהערות אני נגד כתיבה בעברית, בעיקר בגלל עניין השמאל-לימין ברמת הפסקה.
בעניין המסמכים, לעומת זאת, אנחנו צריכים לזכור שהמטרה של מסמכי העיצוב הם להיות ברורים וקריאים ככל האפשר. גם ככה יכולת התבטאות בכתב היא לא ברשימת הדרישות מתפקיד של תכניתן או ארכיטקט, ובפעמים רבות לקרוא מסמך עיצוב הוא תהליך ארוך ומפרך של פענוח כוונה שנסתרת תחת גבב מילים. התוכן המקצועי הוא פעמים רבות מסובך מספיק, והייתי רוצה להציב לכותבים ולקוראים כמה שפחות מכשולים בדרך להבנה. ומה לעשות שבארצנו דוברת העברית, הרוב המוחלט של האנשים כותבים וקוראים עברית שוטפת הרבה יותר מאשר אנגלית. רוב הישראלים, גם אלה שדוברים אנגלית שוטפת, מתבטאים טוב יותר וקוראים מהר יותר בעברית. כמו שיש הצדקה לבלוגים טכניים בעברית וספרי לימוד בעברית, כך יש גם הצדקה לכתיבת מסמכי פרוייקט בעברית. יקח פחות זמן לכתוב אותם. יהיו פחות אי הבנות בקריאה שלהם.
יש שלוש התנגדויות ראשיות שנתקלתי בהן לכתיבת המסמכים בעברית:
1. השפה הטכנית שבה כתובים המסמכים שזורה בביטויים לועזיים, ואין דרך אחת נכונה להתמודד עם זה. חלק מהמונחים אפשר לתרגם אבל רובן נשמעות מאולצות ולא טבעיות. אפשר גם לא לתרגם ולהשאר עם המונחים באנגלית, אבל אז יש את הבעייתיות של שילוב טקסט אנגלי בתוך עברי. איך אני כותב “design pattern” בעברית? והאם אני מציין שאני מממש את ה-Design Pattern של המפעל הסטטי, או של ה-Static Factory? ומה עם מונחים שהם באנגלית, כי הם מציינים שמות שבאים מהקוד עצמו? אני אדבר על “מחלקת Customer”?
יש כאן בעייתיות, אני מסכים, אבל אני חושב שאפשר יהיה להתרגל די בקלות. תרגומים פופולריים (מחרוזת, מסד נתונים) יתורגמו. מונחים אחרים ישארו באנגלית בגוף הטקסט. לא תמיד אפשר למצוא פתרונות מושלמים לשילוב עברית ואנגלית, אבל אנחנו רגילים לזה.
2. תכניתנים צריכים לדעת אנגלית. תיעוד ב-MSDN הוא באנגלית, רוב החומרים והבלוגים בעולם הם באנגלית, ושליטה באנגלית היא רף-כניסה ראוי לתחום. עם הטענה הזו אני כבר לא מסכים לחלוטין. יש הרבה אנשים עם כל הכישורים להיות מתכנתים מצוינים, אבל שהאנגלית שלהם חלשה. זו הסיבה שהוקם האתר הזה, והסיבה שיש User Groups בארץ. אני חושב שיש לתחום כולו רק להרוויח מהורדת רף הכניסה.
ומעבר לכך, העובדה שאדם שולט באנגלית ומסוגל לקרוא ולכתוב בלי בעיה לא אומר שהוא יקרא ויכתוב ביתר קלות בעברית. אנחנו לא מחפשים להערים מכשולים מלאכותיים.
3. חברות בימינו נוטות להיות רב-לאומיות. פרוייקט יכול להתחיל בארץ ולעבור לקבוצת פיתוח בחו”ל. סטרטאפ יכול לתלות את המודל הכלכלי שלו על כך שיקנה ע”י גוגל. כל אלה טיעונים לא רעים בכלל, אפילו משכנעים. זה שיקול פרקטי של השענות על האנגלית כעל ה-lingua franca של העולם, ובטח של העולם הטכנולוגי. אם זה המצב, שווה להשאר באנגלית. אבל אני חושב שזה לא ממש מתאר את רוב המצבים.
1. האמת היא שיצא לי לכתוב משתנים בעברית פעם אחת – ב-SharePoint WebParts, הממשק מייצר Drop Down על פי הערכים של enum, והדרישה היתה שהערכים יהיו בעברית. היה לי קשה עם זה.
2. יש למישהו מונח עברי טוב ל-Context Switch?
Tomorrow I am bidding my laptop farewell, and will soon buy me a new one. This results in what I like to call Settings Anxiety. I spend months tuning various aspects of my computer to my liking, and starting afresh with a new machine is wearisome. There are solutions to this, of course. I can make a full settings backup using Windows Easy Transfer or some such tool to store my settings and registry information and reload them on my new computer. I am, however, leery of it. First of all, I don’t trust WET to backup everything, especially 3rd party settings and application data. Secondly, I don’t want to copy all the cruft that’s accumulated in my user settings. One advantage to leaving the old computer is to start fresh.
So my compromise is to do a set of manual exports for various pieces of software, to carry it with me to the new computer, whenever it arrives. I’m going to list the various programs I use here that I bothered to customize, and detail the steps needed to do a manual export. This way I can come back to it the next time I reformat.
1. Outlook 2007
I use Outlook to connect to an Exchange server and to Gmail via IMAP. Both of these use OST files for local offline caching, but the primary storage is on the server. No real need for me to back this up.
Unfortunately, there’s no way to export my Outlook profile’s entire Accounts list to a file, and reimport it later. It means I always have to go to Gmail’s help page to remember the port numbers they use for encrypted IMAP and SMTP, but it’s not too much of a hassle.
2. Visual Studio 2008
Two important customizations here – color scheme (I like grey background) and keyboard shortcuts (single-click build-current-project ftw!). Both are easily exported using Tools –> Import and Export Settings –> Export selected environment settings.
3. Google Chrome
Bookmarks, cookies and stored passwords – things I really don’t feel like retyping again and again after I got my Chrome to memorize it for me. Basically, with all major browsers importing favorites and settings from one another, you only really need to backup one browser’s settings.
Unfortunately, Google haven’t quite gotten around to implementing Export Settings in Chrome – like a lot of other things. To backup my settings, I am copying the C:\Users\username\AppData\Local\Google\Chrome\User Data folder. There’s a lot of useless junk there – the cache, Google Gears data – but it should contain all the interesting bits too.
4. RSSBandit
RSSBandit’s “Export Feeds” option will probably export the feed definitions, but not the actual text of the actual feeds that I already have, offline, in my reader. It does support exporting the list of items in a feed, including read/unread state, though. But a better solution, assuming I am unbothered by space or time, is to use RSS Bandit’s “Remote Storage” option to save the entire feed database to a ZIP file that can be “downloaded” into a new installation. Under Tools –> Options –> Remote Storage set the destination (I use a file share), then do Tools –> Upload Feeds.
When recovering, I’ve found that RSS Bandit doesn’t always succeed in reading the ZIP file – I got a “file was closed” error. What I did was simply open the ZIP file and copy all the XML files into the %AppData%\RssBandit folder, et voila.
5. Windows Live Writer
Another useful tool that wasn’t given an Export Settings option. There are two things to backup here:
1. My blog settings – I have 5 different blogs I manage through WLW. Here I had to go back to the Registry. Funny how it seems so old-fashioned. Export all settings from HKEY_CURRENT_USER\Software\Microsoft\Windows Live\Writer.
2. Blog drafts and recently-written posts are stored in My Documents\My Weblog Posts. Would be a good idea to back those up as well.
3. When restoring settings, it initially appears that while the blog settings are properly recovered, the drafts aren’t. All you need to do, though, is double-click on one of the .wpost files in the My Weblog Posts folder, and the Drafts and Recently Posted lists will get populated.
6. Digsby
Digsby, the multi-protocol IM and Social Networking tool, uses a centralized login to store the settings of my different protocols – so I only need to log in with my Digsby ID to have all my IM networks working. There are some tweaks to the client itself which I would like to not lose, but they don’t provide an Export Settings feature either, and it’s nothing I can’t live without.
For most other things, I’d rather not bother until I actually need it.
A while ago, I posted an entry about marshalling a managed System.String into an unmanaged C string, specifically a plain char*. The solution I suggested, back in 2007, involved calling Marshal::StringToHGlobalAuto method to allocate memory and copy the string data into it, and then cast the HGlobal pointer into a char*.
It seems that in Visual Studio 2008 a new way of doing it was added, as part of the new Marshalling Library. This library provides a whole set of conversions between System.String and popular unmanaged string representations, like char*, wchar_t*, BSTR, CStringT<wchar_t> and others I am even less familiar with.
The smarter string representations, like std::string, have their own destructors so I can carelessly let them drop out of scope without worrying about leaks. The more primitive ones, like char*, need to be explicitly released, so that’s why the Marshalling Library contains a new (managed) class called marshal_context which gives me exactly this explicit release.
Let’s compare my old code with the new:
1: const char* unmanagedString = NULL;
2: try
3: {
4: String^ managedString = gcnew String("managed string");
5: // Note the double cast.
6: unmanagedString = (char*)(void*)Marshal::StringToHGlobalAnsi(managedString);
7: }
8: finally
9: {
10: // Don't forget to release. Note the ugly casts again.
11: Marshal::FreeHGlobal((IntPtr)(void*)unmanagedString);
12: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
And the new:
1: marshal_context^ context = gcnew marshal_context();
2: String^ managedString = gcnew String("managed string");
3: const char* unmanagedString = context->marshal_as<const char*>( managedString );
Much shorter, I’m sure you’ll agree. And neater – no need for all the icky, icky casting between different pointer types. And most importantly, I don’t have to explicitly release the char* – the marshal_context class keeps a reference to all the strings that were marshalled through it, and when it goes out of scope its destructor makes sure to release them all. Very efficient, all in all.
Here’s a little gotcha I ran into today – if you have code in a class’s static constructor that throws an exception, we will get a TypeInitializationException with the original exception as the InnerException – so far, nothing new.
However, if we keep on calling methods on that object, we’ll keep receiving TypeInitializationExceptions. If it cannot be initialized, it cannot be called. Every time we try, we’ll receive the exact same exception. However, the static ctor will not be called again. What appears to happen is that the CLR caches the TypeInitializationException object itself, InnerException included, and rethrows it whenever the type is called.
What are the ramifications? Well, we received an OutOfMemoryException in our static ctor, but the outer exception was caught and tried again. So we got an OutOfMemoryException again, even though the memory problem was behind us, which sent us down the wrong track of looking for persistent memory problems. Theoretically, it could also be a leak – the inner exception holding some sort of reference that is never released, but that’s an edge case.
Here’s some code to illustrate the problem. The output clearly shows that while we wait a second between calls, the inner exception contains the time of the original exception, not any subsequent calls. Debugging also shows that the static ctor is called only once.
1: class Program
2: {
3: static void Main(string[] args)
4: {
5:
6: for (int i = 0; i < 5; i++)
7: {
8: try
9: {
10: Thread.Sleep(1000);
11: StaticClass.Method();
12:
13: }
14: catch (Exception ex)
15: {
16: Console.WriteLine(ex.InnerException.Message);
17: }
18: }
19:
20: Console.ReadLine();
21: }
22: }
23:
24: static class StaticClass
25: {
26: static StaticClass()
27: {
28: throw new Exception("Exception thrown at " + DateTime.Now.ToString());
29: }
30:
31: public static void Method()
32: { }
33:
34: }
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
More Posts
Next page »