 Blog For Free!
Archives
Home
2004 September
2004 July
2004 June
2004 May
2004 April
My Links
Matt Pietrek's Blog
tBlog
My Profile
Send tMail
My tFriends
My Images
Sponsored
Blog
|
| IconPaNel - New Code Project Article |
| 09.22.04 (7:22 am) [edit] |
|
My latest article (a quickie) is a custom control that provides animated expand/collapse of 'tool items' ala the Windows XP and Longhorn taskbars.
You can find it here
|
|
|
| |
| DualFormatter Serialization |
| 09.20.04 (4:39 pm) [edit] |
I was responding to a post, Deprecating the Soap Formatter, in regard to how the Soap formatter is actually useful on occasion because it is (almost) humanly readable. In the process I mentioned something I wrote called DualFormatter which I use for special situations (debugging message streams or complex object serializations.) Anyway, I hate to post a code listing (but I hate hosting stuff even more) but here it is. ------------------------- ---------- DualFormatter. Allows serialization to occur on multiple streams concurrently. ------------------------- ---------- using System; using System.IO ; using System.Runtime.Serialization ; using System.Runtime.Serialization.Formatters ; using System.Runtime.Serialization.Formatters.Binary ; using System.Runtime.Serialization.Formatters.Soap ;
namespace SerializationUtils { /// /// An implementation of that allows /// serialization to be redirected to multiple streams /// /// /// DualFormatter designates one /// as the primary serializer and routes all calls to /// and to this /// with the specified. /// /// DualFormatter also provides for secondary streams to be serialized (but not deserialized) using /// an alternate stream (by default a ) which can be used for debugging purposes, /// or to compare size deltas of different /// implementations. /// /// /// By default, DualFormatter uses a folder (specified during construction) to hold the files created /// when an alternate stream is serialized. DualFormatter uses a simple algorithm to produces a /// (semi) meaningful name that is also unique. Note: it is only highly probable that the name is unique, but /// not guaranteed /// /// /// By default DualFormatter creates a /// as the primary serializer, and a /// as the secondary stream since this would be the normal case for debugging. Alternate constructors allow /// other variations, including user specified /// implemnetations for both the primary and secondary serializers. /// /// /// Because DualFormatter is an implementation of /// either the primary or secondary serializers (or both) can be a DualFormatter /// /// public class DualFormatter : IFormatter { /// /// The primary implementation /// which performs the (de)serialization to the primary /// private IFormatter primaryFormatter ; /// /// The secondary implementation /// which is used for serialization to an alternate /// private IFormatter secondaryFormatter ; /// /// The folder where alternate instances will be written /// private String serializationFolder ; /// /// An class field used to generate unique sequence numbers for all instances of DualFormatter /// private static int seqNum = 0 ; /// /// Default constructor which creates a /// for primary serialization, and a /// for serialization to an /// /// The folder where secondary are created /// /// If the specified folder does not exist, it is created. This operation can throw various exceptions. /// For more information /// /// If serializationFolder is public DualFormatter(String serializationFolder) { if (serializationFolder == null) { t hrow new ArgumentNullException("Du alFormatter: Serialization folder cannot be null","serializationFolde r") ; } this.serializationFolder = serializationFolder ; if (!Directory.Exists(serializationFolde r)) { D irectory.CreateDirectory(serializa tionFolder) ; } primary Formatter = new BinaryFormatter() ; seconda ryFormatter = new SoapFormatter() ; } /// /// Construct a DualFormatter using the specified /// as the primary serializer /// /// The primary serializer /// The folder where secondary are created /// /// If the specified folder does not exist, it is created. This operation can throw various exceptions. /// For more information /// /// The secondary serializer is an instance of /// /// /// public DualFormatter(BinaryForma tter primaryFormatter,String serializationFolder) { if (serializationFolder == null) { t hrow new ArgumentNullException("Du alFormatter: Serialization folder cannot be null","serializationFolde r") ; } if (primaryFormatter == null) { t hrow new ArgumentException("DualFo rmatter: Primary IFormatter cannot be null","primaryFormatter") ; } this.serializationFolder = serializationFolder ; if (!Directory.Exists(serializationFolde r)) { D irectory.CreateDirectory(serializa tionFolder) ; } this.primaryFormatter = primaryFormatter ; this.secondaryFormatter = new SoapFormatter(primaryForm atter.SurrogateSelector,primary Formatter.Context) ; } /// /// Construct a DualFormatter using the specified /// as the primary serializer /// /// The primary serializer /// The folder where secondary are created /// /// If the specified folder does not exist, it is created. This operation can throw various exceptions. /// For more information /// /// The secondary serializer is an instance of /// /// /// public DualFormatter(SoapFormatt er primaryFormatter,String serializationFolder) { if (serializationFolder == null) { t hrow new ArgumentNullException("Du alFormatter: Serialization folder cannot be null","serializationFolde r") ; } if (primaryFormatter == null) { t hrow new ArgumentException("DualFo rmatter: Primary IFormatter cannot be null","primaryFormatter") ; } this.serializationFolder = serializationFolder ; if (!Directory.Exists(serializationFolde r)) { D irectory.CreateDirectory(serializa tionFolder) ; } this.primaryFormatter = primaryFormatter ; this.secondaryFormatter = new BinaryFormatter(primaryFo rmatter.SurrogateSelector,primary Formatter.Context) ; } /// /// Construct a DualFormatter specifying both the primary and secondary serializers /// /// The primary serializer /// The secondary serializer /// The folder where secondary are created /// /// If the specified folder does not exist, it is created. This operation can throw various exceptions. /// For more information /// /// The secondary serializer is an instance of /// /// /// public DualFormatter(IFormatter primaryFormatter,IFormatt er secondaryFormatter,String serializationFolder) { if (serializationFolder == null) { t hrow new ArgumentNullException("Du alFormatter: Serialization folder cannot be null","serializationFolde r") ; } if (primaryFormatter == null) { t hrow new ArgumentException("DualFo rmatter: Primary IFormatter cannot be null","primaryFormatter") ; } this.serializationFolder = serializationFolder ; if (!Directory.Exists(serializationFolde r)) { D irectory.CreateDirectory(serializa tionFolder) ; } this.primaryFormatter = primaryFormatter ; this.secondaryFormatter = secondaryFormatter ; } /// /// Advance (and return) the class level sequence number in a thread-safe manner /// private int SequenceNumber { get { l ock(this) { & nbsp;return ++seqNum ; } } } #region IFormatter Members /// /// Serialize the object graph to the specified stream using the primary /// /// /// The stream where the object graph is serialized /// The object graph to be serialized /// /// If the secondary serializer is non- then an attempt is made /// to serialize the object graph to an alternate . /// /// Any errors serailizing to the alternate are trapped /// and will not be thrown back to the main code. /// /// /// The alternate stream is created via the method /// /// /// public void Serialize(Stream serializationStream, object graph) { primary Formatter.Serialize(serializationSt ream,graph) ; if (secondaryFormatter != null) { S tream stream = null ; t ry { & nbsp;stream = GetSecondaryStream(graph, secondaryFormatter.GetType()) ; & nbsp;secondaryFormatter.Serialize(stream,graph) ; } catch(SerializationExcept ion se) { & nbsp;System.Diagnostics.Debug.WriteLine("Unexpected SerializationException writing secondary stream: " + se) ; } catch(Exception e) { & nbsp;System.Diagnostics.Debug.WriteLine("Exception writing secondary stream: " + e) ; } finally { & nbsp;if (stream != null) { & nbsp; try { stream.Close() ; } catch {} & nbsp;} } } } /// /// Get/Set the used by /// the serializers /// public SerializationBinder Binder { get { r eturn primaryFormatter.Binder ; } set { p rimaryFormatter.Binder = value ; i f(secondaryFormatter != null) { & nbsp;secondaryFormatter.Binder = value ; } } } /// /// Get/Set the used by /// the serializers /// public StreamingContext Context { get { r eturn primaryFormatter.Context ; } set { p rimaryFormatter.Context = value ; i f(secondaryFormatter != null) { & nbsp;secondaryFormatter.Context = value ; } } } /// /// Get/Set the used by /// the serializers /// public ISurrogateSelector SurrogateSelector { get { r eturn primaryFormatter.SurrogateSelector ; } set { p rimaryFormatter.SurrogateSelector = value ; i f(secondaryFormatter != null) { & nbsp;secondaryFormatter.SurrogateSelector = value ; } } } /// /// Deserialize using the primary serializer /// /// The containing the object graph /// to be deserialized /// /// The resulting object graph /// /// /// The secondary serializer(s) is not involved in the deserialization process /// public object Deserialize(System.IO.Stream serializationStream) { return primaryFormatter.Deserialize(serialization Stream) ; } /// /// Method to create an alternate stream used by the secondary serializer /// /// The object graph /// The of the secondary serializer /// /// A where the secondary serialization is written /// /// /// The default implementation creates a in the /// with a unique name created from the type name of the /// object graph, the current year, month, and day, and a unique, monotonically increasing /// sequence number /// protected virtual Stream GetSecondaryStream(object graph,Type formatterType) { DateTim e now = DateTime.Now ; String graphName = graph.GetType().FullName + String.Format(" - {0}.{1}.{2}.{3}",now.Year,now.Month,now.Day,SequenceNumber) ;
if (formatterType == typeof(SoapFormatter)) { g raphName += ".soap.xml" ; } else if (formatterType == typeof(BinaryFormatter)) { g raphName += ".bin" ; } else { graphName = "." + formatter.GetType().Name ; } return new FileStream(Path.Combine(serializationFold er,graphName),FileMode.Create,FileAccess.Write) ; } #endregion } }
|
|
|
| |
| Avalon Complexity |
| 09.20.04 (7:06 am) [edit] |
|
I stumbled across a discussion on API Complexity (related to Avalon) at simplegeek. As someone who is at least midly curious about the future of Windows, especially related to the user experience I found the overall post and Slashdot thread quite interesting.
A few posts later I ended up here reading an Avalon developers views on the overall subject expressed as a *very long* metaphor regarding cars, transmissions, and driving. To me this post is uber-scary and sends up a lot of red-flags regarding Avalon. Not because of the views expressed, but the complexity with which it is expressed. Metaphors are supposed to make complex things simpler to understand by relating the complexity in a way that everyone can understand. 1st, by using a metaphor you are basically admitting that you can't explain the complexity in a way that even the experts involved in the discussion can understand, and 2nd KISS. If I wanted to read about cars and transmission I'd subscribe to Car & Driver.
Honestly, while reading this post if felt pangs of agony shooting to my brain, begging me to just close the reader. Somehow I managed to get through to the end but I lost most of the point early on when my eyes glazed over.
Of course the author (IanG) does apologize for the long-winded metaphor, but not until after all is said (and said, and said) and done.
Anyway, I am now very wary of Avalon. Strange what a single blog post will do. But I am getting the feeling that outside the Microsoft camp that Avalon is a big *yawn* anyway. Still, I like what I see so far, so heres hoping :)
Tom
|
|
|
| |
| Geek Royalty Saturday |
| 09.15.04 (5:22 pm) [edit] |
bout two weeks ago I was most fortunate to be able to get away from the perpetual grind and kick-back with family and friends for a long evening of conversation inter-mixed with kids, cats, and a lot of food and wine. Although it is rare that my wife and I entertain, it was a special occasion seeing as how my old friend and ex-compatriot Matt Pietrek was in town with his charming fiance and his six year old son. We were also joined by my good friend John Garland (a.k.a, Gator) and his wife Karen, and eventually the notorious Chris Jackson, a salty dog of an engineering manager who cut his teeth on network management products at Cabletron (back when it was in its prime.)
Of course having Matt back for the 1st time since his March departure to the 'Evil Empire' was a special treat, and as Gator would say, Matt is 'Geek Royalty' so you probably expect some heavy technical discussions to pursue. Alas, that is rarely the case when we get together as neither Matt nor I have a blazing desire to discuss technologoy or cool code tricks. In light that all our better halves were hanging out with us I think they are pretty relieved when we talk about non-tech stuff like engagement rings, kids, houses, hiking, and building island resorts where none existed before. Still, you would be pretty disappointed if we didn't at least talk about something related to technology or the business of technology wouldn't you?
Anyway, heres the quick synopsis:
- Matt and I are big fans of Raymond Chens blog: The Old New Thing. The guy just speaks our language because we lived the x86 architecture (and how Microsoft uses it) for many, many, many years. Raymonds stuff is like an old worn shoe. Very comfortable
- Gator admitted to having 'avoided the whole Blog thing .' The next Monday he was running Thunderbird 0.8 and getting into the swing of it...
- Matt raved about the Index servers at Microsoft. John and I laughed. We don't have enough developers and/or shared code to make an index server useful
- John and I raved about One Note and Matt gave us a blank stare. Hopefully he'll get turned on to it. Best Microsoft product in years
- Of course we talked about the whole WinFS, Longhorn, Avalon announcements (you know the ones.) Everybody just shrugged no big deal, seems like a good idea to get some of the technologies out earlier, especially Indigo on XP
- We talked about new vangaurd of technical authors/writers (Chris Sells, ???) and how the old guard (Matt, Jeffery, Don, et. al.) are still highly regard (yes, 'Geek Royalty') but the times they are a changing...
- We found out the Gator's wife Karen has a serious Feta Cheese fetish and makes great Wine selections
During our discussion of authors and the state of the world regarding technical diatribe I said that I thought that Jeffey Richter was the best author (by a hair.) I think the Jeffery does a great job with very large volume subjects (go all the way back to Advanced Windows NT) and keeps it all coherent, easy to ready, and 98% accurate.
Jeffery isn't my favorite writer but he is the consummate professional and never hacks the book. Hopefully Matt wasn't offended (why are we always letting our egos get in the way of everything) but he knows I think his writing is the most interesting. Matt's writing comes from the passion of 'technical digging' (Matt used to refer to this as spelunking.) and he is a professional developer (sorry Jeffery), not a professional writer. That's why I love Matt's stuff, but repsect Jefferies work given his super high-quality on very large subjects.
One thing I found interesting was that Matt thought the John Robbins (also ex-numega) was the best writer. I can't quote Matt but to paraphrase him, he said something to the effect that "I stuggle to write/explain something, and then I read John and he just finds the perfect way." Again this is nowhere near a direct quote but I hope Matt will agree that it is not a gross misrepresentation.
The few things I have read by JR have been good (and Gator swears by his debugging book), but the problem I have with JR's stuff (and JR, this is not personal), he barely writes about anything I find interesting. Damn it JR don't write about converting C# to Visual Basic and/or Visa Versa. Who cares? Get back into doing the 'Juval Lowy thing' and write about practical stuff. I read JR's two-part very-tiny-mini-serieis on localization (yes, localization is very boring) but it is that everyday stuff I need to know before I implement (not six months after I ship the product.) Those articles (boring as the subject is) have a permanent place in my desk because they are practical and applicable on a daily basis.
Anyway, next year you are all invited. Bring your kids so that they can entertain my kids. That way, Shel and I can actually enjoy the company and conversation.
Peace out to Matt, Congrats to you and Carrie! Sorry you missed the Monster Magnet show, but hopefully the trip east had its own rewards.
|
|
|
| |
| Read Juval Lowy (or else!) |
| 09.15.04 (3:59 pm) [edit] |
|
The October issue of MSDN Magazine contains an article by Juval Lowy called Advanced Serialization: Format your way to success with the .NET Framework Versions 1.1 and 2.0
The article is not brilliant, it's not earth shattering, and it's not sexy, but if you are a serious professional developer you need to read everything this guy writes; articles, books, poetry, limericks, sonets, ...whatever!
'Why?' you ask. I will tell you plain and simple. He is is the only author out there who is really telling you what you need to know. He is going beyond the technology (because the technolgoy part is not hard is it?) and telling you how to apply it. Are the topics sexy? Hardly ever. The crux of his writing is about proper application of basic technologies such as events and delegates, serialization, and assembly versioning. These are all things that you can and will apply everyday.
A good example is serialization. If I asked you 'You have serialized objects from version 1.0 of your application and now that you are getting ready to plan the release of version 2.0, what will you need to do to automatically upgrade existing clients to the new object versions when 2.0 is released?'
The 2nd question would be: 'Knowing this, what would you do different in version 1.0 to make the work involved for a version 2.0 upgrade radically simpler?'
If you can't answer these questions at serious length then you are reading the wrong authors. Of course, who can blame you when you could be reading 'Use the Visual Studio 2005 Bootstrapper to kick-start your installation'? Not a bad article but your time is much better spent focused on things you will use day-in and day-out to improve your productivity, quality of work, and ultmately, your quality of life. That is the worthwhile investment.
I think part of the problem is that good developers start to turn a deaf ear to these types of things because we start to want to avoid admitting what we don't really know. We know enough about a given subject so just leave us alone; we don't want to admit that we don't 'own' the subject.
Serialization is a case in point. Most .NET developers probably think they more than enough about serialization. Of course they read Jeffery Richter's 3-part MSDN articles about the 'technology', and filed a few useful facts away. Then they went on to practice basic serialization techniques until they were/are forced to do otherwise. A viable enough strategy if you don't mind having to work your way out of serious jams down the road, but then again, that's what we experts do isn't it? I guess it's just part of the mystique
Anyway, the most serious fallacy of my diatribe is that even good developers rarely get a shot a 2.0, and when we do, we often don't change the fundamentals (thus avoiding serious issues), or we write it off as the price of success. Same argument I used when people asked me about internationalization: most developers are lucky to get a product release in their 'native' language, much less someone else's.
As far as bad developers go, they don't know any better (yet), or they don't care because they won't be around for version 2.0 and it will be SEP (Someone Elses Problem.)
|
|
|
| |
| Finally got an article update out (whew!) |
| 07.25.04 (5:30 pm) [edit] |
Another long strange trip that started out with a cool new control which was supposed to be a "light" article (keeping things moving fast while maintaining high quality and attention to detail is getting difficult...) and although I finished the control (it's a secret for now...) I never finished the article cuz I got sucked back into my previous article (XPPanel.)
Anyway, trying to use the XPPanel in the real-world was a bit of a pain, especially trying to layout things like 'Detail' panels. So I created two new controls TextLayoutPanel and ItemLayoutPanel that intelligently handle these types of things. Writing them was a cinch, but I ended up spending a week twisting and turning the code trying to get the designer to do what I wanted. I finally gave up with no gain and a lost week of effort.
Of course it was frustrating so I ended up going back and tracking down every good article/posting on writing designers that I could find. In this particular case I had really come to the conclusion that my design approach just didn't fit with what the designer wanted/supports. Since I liked the way I had things implemented, I didn't want to fundamentally redesign it. Instead, I committed to re-reading all the tech. info I had on designers so that I could re-enforce my grasp on some areas that still seem a bit opaque. Anyway, I was just sort of haphazardly looking at a posting that I had previously ignored when, completely non-sequitor to the specific content of the article, it just hit me!
It was so simple and obvious but my pre-disposition to moving fast (and not thinking the problem through) had completely blinded me to the very simple answer. The next morning I had it working *exactly* the way I wanted and eliminated a lot of code that just wasn't necessary once the designer integration was 100%.
The specific design problem (and solution) doesn't really translate, but the points are:
1) The designer is pretty damn flexible and generally won't prevent you from achieving the type of integration you need
2) The designer is pretty damned opaque and when you get stuck it is important to go back to the fundamentals because the answer is there somewhere
Supposedly WhidBey is fixing some of these issues (I have a DVD with the beta on it but troubling getting a DVD player to work with my laptop. I only use the laptop for goofing so I dont care what Whidbey does to it.)
Anyway, I thought I would post the URL's for all the designer articles that I think really provide good information on the various designer subjects. You've probably seen most of them, but having them all in one place seems convienient.
From divil.co.uk:
[url=http://www.divil.co.uk/net/ar...]Hosting Windows Forms Designers[/url]
[url=http://www.divil.co.uk/net/ar...]Introduction to Designers[/url]
[url=http://www.divil.co.uk/net/ar...]Creating Collection Controls with Rich Design Time Support[/url]
From MSDN:
[url=http://msdn.microsoft.com/lib...]Enhancing Design-Time Support[/url]
[url=http://msdn.microsoft.com/lib...]Design-Time Attributes for Components[/url]
[url=http://msdn.microsoft.com/lib...]Customizing Code Generation in the .NET Framework Visual Designers[/url]
[url=http://msdn.microsoft.com/lib...]Writing Custom Designers for .NET Components[/url]
[url=http://msdn.microsoft.com/lib...]Getting the Most Out of the .NET Framework PropertyGrid Control[/url]
[url=http://msdn.microsoft.com/msd...]Building Windows Forms Controls and Components with Rich Design-Time Features (Part 1)[/url]
[url=http://msdn.microsoft.com/msd...]Building Windows Forms Controls and Components with Rich Design-Time Features (Part 2)[/url]
[url=http://msdn.microsoft.com/lib...]Make Your Components Really RAD with Visual Studio .NET Property Browser[/url]
[url=http://msdn.microsoft.com/lib...]Creating Designable Components for Microsoft Visual Studio .NET Designers[/url]
[url=http://windowsforms.net/artic...].NET Shape Library: A Sample Designer[/url]
|
|
|
| |
| XPPanel CodeProject Article |
| 06.05.04 (5:52 am) [edit] |
In my previous entry I mentioned changing gears at work which led me down a strange road to developing a new custom control and an article for CodeProject. Anyway, I had to stop my day-to-day development in preperation for a new project in order to write some actual 'software requirements'. Outlining all the base requirements (from the Marketing POV) took less than a day, but I was expected to chug on writing more requirements because that was the 'phase'. Anyway, I got bored quickly and turned to visualization of the new UI where I needed to reduce the infinite number of possibilities into something more realistic. Anyway, I quickly got bored with that also, and just needed to write some code. As the new project was going to very UI intensive I decided to get back into the swing of things by writing a custom control.
For whatever reason I chose an Windows XP style collapsible panel since I wanted to use it in the new UI. Of course I did a lot of homework since there are quite a few open source and commercial implementations available. I couldnt find one that met all my needs, and in many cases I considered the overall production quality of these controls to be somewhat suspect.
So, of course, I decided to roll my own. The goal was to get a high-level of functionality, with high-quality, and good IDE/designer integration. In the end I am reasonably happy with the result, and although you have a lot of different choices, I hope you check out my control if and when CodeProject makes it available.
|
|
|
| |
| FxCop Serialization Best Practices side-tracked |
| 06.05.04 (5:41 am) [edit] |
After a bit of a hiatus from this blog, I am finally back with an update. Last time I wrote an entry I was looking at FxCop and writing some custom results for Serialization best practices. I did continue down that path but do to some basic frustrations and changing gears at work I left it in a nebulous state. I will get back to it, but I will outline my basic frustrations here so I dont forget.
First and foremost, when analyzing classes I find that there are subtle degrees of correctness/incorrectness . Especially if you are looking at best-practices where some technique (or lack there of) may not be incorrect, it is just (not) recommeneded. Unfourtunately FxCop has difficulty in this area as each rule generally needs to be very specific. I find myself writing 3 rules that are almost identical with only minor variations. In many cases all three rules will trigger and report. If I was able to combine rule logic into a rule-set then I could do the analysis in one place and report the rule(s) that made the most sense in the given context. Instead each rule is its own island and any context is thrown out the window. I am sure there are clever ways to overcome this, but I dont have time for clever.
Overall its not that big a deal, especially considering the value of FxCop in the development process. Still, it would be simpler if things were a bit more dynamic and I could group a set of rules into a single analysis/class and fire one or more rules from there.
In the end, my effort will continue and I will eventually post the rules for others to use :)
|
|
|
| |
| FxCop Custom Rules |
| 05.02.04 (8:20 am) [edit] |
If you read the previous post FxCop on Duty then you already know I am a big believer in using FxCop 1.30 to do stricter checking of 'best practices' than can be done by my (weary) eyes alone.
So, I guess it was a natural progression from 'power' user to rule author. At a minimum, I wanted to understand what was involved so that I could better evaluate the cost of extending FxCop for my development. I am a big believer in low-cost/high-benefit and my initial foray into custom rules shows that extending FxCop is relatively straightforward, and although I hit a few initial bumps, overall it was a highly successful endeavor.
Still, I ran into quite a few issues that would be easily resolved by better, more complete examples of using the FxCop SDK, more concise documentation, and a little more forethought from the FxCop designers regarding basic use-cases for rule designers. Obviously, the FxCop team is the ultimate example of rule designers, but given some basic design limitations/flaws, they must suffer from "expert" or "do it this way" syndrome.
Before anyone thinks I am knocking FxCop or the FxCop team, I personally rate the product an 11 on a scale of 1 to 10. But read about my experience and decide for yourself.
Because I have been writing a lot of 'best practices' related to .NET serialization, I wrote a simple program that violated a basic rule, ran FxCop and it didn't report anything useful regarding the problem. In the back of my mind I was pretty sure there was a built in rule that covered this case, but it didn’t matter since I was just playing around. Of course when no rule violation was reported I figured it was something that got left out.
I grabbed the FxCop documentation and did a quick check of the base interfaces (IRule and IReflectionRule), examined how to use an XML "resource" file to describe a rule (rather than hard-coding everything), and created an assembly with one rule that derived from BaseReflectionRule.
So my first mistake was assuming the XML file would be read from disk (yeah, I missed the whole resource thing), of course FxCop complained, and I eventually resolved the issue. Now the rule loaded successfully, but once again there was no violation. Hmmm. So the rule was an ITypeRule, applied at the type level. The internal logic for the rule was simple but it might have been flawed in some way, so I hard-coded a direct return of a violation (no conditional logic.) FxCop again reported no violation. I added some debug output code. Nothing showed up in DebugView. I added an implementation of IAssemblyType and IParameterRule. No violations. What was going on?
I tried changing my override of NestedTypeProtectLevel to PermissionLevels.All. No effect (of course I didn’t expect one.) Then it struck me that the documentation for BaseReflectionRule described the default protection levels. By default, implementations of Type rules that derive from BaseReflectionRule only inspect public types. My test class was not public! A quick change and everything worked as expected (almost.)
Now analyzing my test program caused two violations. My violation and that imaginary FxCop 'Usage' rule violation covering the exact same scenario.
At this point I immediately added overrides for TypeProtectionLevel (and MemberProtectionLevel just-in-case) to return PermissionLevels.All. In my opinion, a vast majority of the serialization rules shouldn't be affected by 'access' level of the type. Again, IMO the FxCop built-in rules for Usage rules regarding serialization should NOT be restricted to public types. This came as a HUGE surprise and a lot of my previous analysis of production libraries is of dubious value. This may be a simple oversight by the rule designers who use BaseReflectionRule and simply forget to override the protection levels. Can you imagine how long this might have gone undiscovered if I had made my test class public from the start?
During this whole 20-minute 'debugging' episode I found it very difficult to determine whether the rule had actually been triggered and failed. You can get information on each rule but there is no obvious piece of information that says 'this rule was called 13 times and found 5 violations.'
Secondly, after I added more Check methods (for IAssemblyRule and IParameterRule) they never fired either. Obviously, protection levels wouldn’t apply to an IAssemblyRule so that was another mystery. What I determined was that if I implemented ITypeRule, the fact that I also implemented IAssemblyRule was irrelevant. It was ignored. Only implementing IAssemblyRule caused its Check method to be called. Ergo, I found out the hard way that a rule should only implement one rule type, and there is obviously some internal algorithm that selects which rule type will actually be applied. As far as I know this isn’t documented (at least not in an obvious place.) Of course, it is common sense.
Finally, I added two more rules as a continuation of the overall process and immediately decided that cutting & pasting implementations was for the birds and decided to write a 'base' class for serialization rules that derived from BaseReflectionRule. The base class provided a simplified constructor and the appropriate protection level overrides. Now my rules were essentially a shell class with a Check method. Sweet. Everything had been factored into the base class except the actual rule logic. I also had a place to put common methods that might be shared by rules.
I fired up FxCop for the final test, and everything worked great, but I got a warning that my base class wasn't a proper rule (correct!), but this could be confusing to 'other' users of my rules. What we need is a way to identify a rule (other than implementing IRule), or we need a way to identify that a Type is not a rule even though it implements IRule.
All in all these were all minor issues, but due to time pressures, I hurried through everything and ended up tripping over a lot of silly design issues. I am alarmed that the built-in serialization rules are only applied to public types and I plan on re-implementing them in my custom rule library so that they are always checked.
So here is the short list of my best-practices for writing custom rules:
Always use BaseReflectionRule and provide rule information as an assembly resource in XML format
Only implement a single IXxxType interface
Carefully evaluate your rule(s) needs regarding Type, Member, and NestedMember protection levels
I don’t have any convenient place to publish my serialization rules (even if they were complete), but the FxCop team provides a location on GotDotNet. I noticed that it didn’t contain any uploads, so maybe if I get around to finishing the rules in the near future, I'll be the 1st.
(P.S. I wrote this entry using Lutz Roeder's 'Writer' application. A simple HTML editor that has only one obvious flaw: no spell checking)
|
|
|
| |
| FxCop on duty |
| 04.30.04 (5:42 am) [edit] |
After shaking off Gator's comments about my blog being called 'Cynical Old Bastard', I spent most of the rest of the day running [url=http://www.gotdotnet.com/team...]FxCop[/url] on an assembly with some relatively old code.
FxCop is a great (IMO indispensable) tool for .NET developers. Although you might decide to call it FxNazi because it 'poo-poo's' your favorite development techniques/styles, it really does a good job of finding holes that violate .NET best practices.
I came across an interesting rule violation that I hadn't seen before:
[url=http://www.gotdotnet.com/team...]Explicit Implementation Rule[/url]
which occurs by explicit (bad?) design on my part.
I generally like to define base interfaces as non-mutable. I.e., all the methods are [i]getters[/i], and there are no [i]setters[/i]. Then I derive a mutable form from the non-mutable. This is not a big deal if you use methods, but when you use properties it causes problems. For example:
public interface INonMutable { Mode { get ; } }
public interface IMutable : INonMutable { [b]new[/b] Mode { set ; } }
A typical implementation might end up looking like this:
public class DefaultThing : IMutable { public Mode { get { return mode ; } }
[b] bool IMutable.Mode { set { mode = value ; } }[/b] }
In this case, IMutable.Mode is an explicit method implementation and it is private/inaccessible to clients as well as derived classes, unless they cast to the instance to IMutable. In this example, IMutable is public (and it has to be because DefaultThing is public) so that is not a problem (although it is a bit ugly.)
Now if we make IMutable non-public, and DefaultThing as well, then we no problems what-so-ever because there is no possibility of derivation, and all clients will see DefaultThing as an instance of type INonMutable without the 'accessibility' to cast it to IMutable. This is a great technique for .NET remoting (regardless of Marshal-By-Value or Marshal-ByReference) when we dont want the client changing an objects state (even if the changes are harmless.)
But, when the types are public the design can cause problems because a derived class may re-implement IMutable.Mode.Set and attempt to call the base class method (without casting to IMutable), in which case they actually end up calling thier own implementation until a stack-overflow occurs do to infinite recursion.
If we want to use this (public) technique we really need to change IMutable.Mode to the following:
new bool Mode { get ; set ; }
and implement IMutable publically, and make the INonMutable.Mode the explicit implementation. Then we can call the INonMutable implementation of Mode from the IMutable.Get implementation. Now everyone is happy.
public class DefaultThing : IMutable { private bool mode = false ; public bool Mode { get { return ((INonMutable)this).Mode ; } set { mode = value ; } }
bool INonMutable.Mode { get { return mode ; } } }
But, after all this, if the IMutable type is public there is little value in seperating the two interfaces as any client can just cast a DefaultThing posing as INonMutable to IMutable. Still, it is not a bad thing as you are forcing the client to do a 'naughty' thing which in an of itself may be enough. Later you can throw him/her a curve ball by giving him an instance that really is INonMutable and say 'I told you so' when they get a cast exception trying to cast it to IMutable.
Also note that the 2nd form of IMutable (which has both [i]get [/i]and [i]set[/i]) is preferred because FxCopy will complain about a property that only implements [i]set[/i].[LINE] [LINE]
|
|
|
| |
| .NET TimeSpan Constructor Design Flawed |
| 04.28.04 (8:25 am) [edit] |
The TimeSpan Structure of the .NET Framework Library has constructors with the following form:
public TimeSpan( int days, int hours, int minutes, int seconds, int milliseconds );
public TimeSpan( int days, int hours, int minutes, int seconds );
and ...
public TimeSpan( int hours, int minutes, int seconds );
Whats wrong with the picture? The constructor that takes 3 arguments drops the 1st argument (not the last!)
So, using the integrated help I was looking at the 4 and 5 argument constructors and when I started writing the code, Intellisense showed the 3 argument form and because all of my arguments except the 1st (days!) were zero I just decided to use the 3 argument form. What could go wrong? ACK!
[b]new TimeSpan(60,0,0) ;[/b]
Instead of getting the 60 day period I expected, I unknowingly got a 60-hour period! Of course around 72 hours later I discovered the problem, but I really felt like an idiot for "trusting" the TimeSpan designer(s).
When you design an interface and you have overloaded methods that take a varying number of arguments, design the the most complete form 1st, then create simplified overloads by dropping parameters from the [b]right[/b] (not the left.)
The principle is this: Generally the most important/significant parameter is the left-most. Users generally assume that it is always present. Making users of your interface have to stop and think (interrupting flow) or worse, double checking the documentation is a sign of sub-par design. Think about how these types of issue interrupt your flow and productivity, then "Do unto others as you would have them do unto you."
My recommendation is that Microsoft mark the three argument form of the constructor with the [Obsolete] attribute (deprecated) so that people stop using it.
|
|
|
| |
| Introduction |
| 04.28.04 (8:05 am) [edit] |
Welcome to my [u]Software Development Stones[/u] blog.
As far as the name goes I guess its about consistently focusing on the smaller, manageable pieces of software engineering that can be applied each-and-every day. I contrast this to all the Project/Development Managers and VP's of Engineering who are always talking about "breaking the big rocks..."
Here is a quote to give you some philisophical context:
[i]The man who removes a mountain begins by carrying away small stones - Anon[/i]
Stones are best practices, clear and simple. Things that dont require process, methodology, or team synchronization for success; they can be applied by the indiviual to make his/her endeavors more productive, higher quality and generally increase the overall success and enjoyablility of developing software.
Stones are generally wrought from mistakes and the following quote sums it all up:
[i]Use missteps as stepping stones to deeper understanding and greater achievement -- Susan Taylor[/i]
As an individual I am pretty open minded, language and OS agnostic, and highly aware that 'context' is a major influence what is best practice. In that regard, I generally dont preach, always welcome feedback, stories, critical commentary, or even just a simple 'hello' from interested readers.
[LINE]
|
|
|
| |
|
|