.NET Forum / Languages / C# / November 2006
singleton ienumerable, need to add more...
|
|
Thread rating:  |
David - 16 Nov 2006 22:10 GMT Hi all,
I have a singleton ienumerable that collects data from a database. I have listed the code below. It has the usual methods of current, move and reset.
I need to go to a certain position or set filters or many other things and I don't know how to do this. I have just learned singleton AND ienumerable, so miraculously got this to work so far. I could do with a bit of help to go further with it. If need by, I may have to change the type to achieve the aim.
What I want to do next is to select a specific row by passing in an index. I don't know how to do this at all and no matter what I try, I just can't compile.
My source is below...
All help is appreciated.
public class Folders : System.Collections.IEnumerable, System.Collections.IEnumerator { private static Folders mObj; // This is used to manage the singleton. private DataTable DT;
System.Collections.IEnumerator ienum;
// This is the singleton constructor private Folders() { InitData(); }
// This sets up the singleton. public static Folders SiteFolders { get { if(mObj == null) // only one instance is created mObj = new Folders();
return mObj; } }
private void InitData() {
// Get my data from the database and pass into ienum ienum = DT.Rows.GetEnumerator();
}
public System.Collections.IEnumerator GetEnumerator() {
// Polymorph this object into an IEnumerator interface return (System.Collections.IEnumerator)this;
}
public void Reset() { ienum.Reset(); }
public bool MoveNext() { return ienum.MoveNext(); }
public object Current { get { return new Folder((DataRow)ienum.Current); } } #endregion
public object SelectFolder(int myRow) { // get // { return new Folder((DataRow)myRow); // } }
}
I have tried using square brackets for the selectfolder as well, but just won't compile. How can I select a specific row? Incidentally, the Folder that selectfolder is referencing just takes my datatable and passes it into some properties.
Because I have just learned about the singleton and ienumerable, I really do not know how to continue. :-(
Best regards, Dave Colliver. http://www.AshfieldFOCUS.com ~~ http://www.FOCUSPortals.com - Local franchises available
Marc Gravell - 16 Nov 2006 22:28 GMT OK, one thing at a time.
I think the "indexer" syntax you are looking for is:
public Folder this[int rowIndex] { get { return new Folder(DT.Rows[rowIndex]); } }
you can then access Folders.SiteFolders[4]; (etc)
Note that I tweaked the return type to be useful. However, the bigger points:
* it is unusual for something to be enumerable and an enumerator; I suspect you just mean IEnumerable or IEnumerable<Folder> * Thread safety: since you appear new to C# this probably won't be an issue, but in general static methods should be thread safe, which SiteFolders isn't * could just pass up the obtained enumerator, rather than the smoke'n'mirrors? * each call to Current returns a different object; this could get very, very confusing, and lead to all sorts of fun * minor comment issue: this is merely a cast, not polymorphism * not sure this is really suited to a singleton pattern - although that might just be that I don't know enough about the context, so I'll give you the benefit of the doubt
Given that you seem to have a wrapper object (Folder), there may be a lot of benefit in moving to a List<Folder> implementation; this would allow you to hand over a simple enumerator without all the fuss, and your Folder objects would only exist once each- you'd just loop over the DataTable.
Let me know if you want more help,
Marc
David Colliver - 17 Nov 2006 09:56 GMT Thank you Marc,
The initial enumerable info I found on the net somewhere, as also the singleton.
I have had quite a battle to get them both to work,
My background is classic ASP, having moved into .net a couple of years ago, but still having large gaps in my knowledge.
I did try something similar to the public Folder this[int rowIndex] but kept getting errors. However, I don't think I had the "this" word. I will try that.
For your points... 1. I assume this is with the two items that the class is derived from. Can I just remove the IEnumerable and it will work? 2. At the moment, thread safety is not an issue, but is likely to be as the project progresses. How should I write it to be safe? 3. (Pass the obtoned enumerator) Can you give me some pointers? code examples or URLs? 4. Again, this was just copied. I understand that an ienumerable has to have a min of 3 items. reset, movenext and current. What should I be doing here? 5. polymorph... This is a direct copy. Having not covered polymorphing, I couldn't really question this, though I can quite clearly see (now you mention it) that it is a cast. 6. I have had a 24 message thread in aspnet trying to acheive this. What I am wanting is a "global" datatable that is enumerated. I want to be able to just simply assign a variable without having the "new" operator. An example...
I wanted to have the folders loaded from the database. I DID NOT want to have to go to the database every time, as a DB call is expensive.
I wanted all my controls, Page, UserControls and Custom Controls to be able to call the same instance of data. Having a "new" operator would instantiate the data each time, thereby calling the database each time. If you are familiar with Microsoft CMS, I want to do something like...
Channels MyChannels = CmsHttpContext.Current.Channel.Channels;
foeach (Channel ch in MyChannels) { Response.Write ch.DisplayName; }
WantedChannel = MyChannels.Channel.GetByGuid("guid-string");
Notice, no "new".
Thank you.
Regards, Dave Colliver. http://www.AshfieldFOCUS.com ~~ http://www.FOCUSPortals.com - Portal franchises available
> OK, one thing at a time. > [quoted text clipped - 36 lines] > > Marc Marc Gravell - 17 Nov 2006 11:02 GMT 1. Actually I think it is IEnumerator that is unnecessary / confusing matters
2. Either you need a static lock variable somewhere, or alternatively you could simply take the hit and initialise the field in the static ctor (which is thread-safe by the CLR). Jon discusses the various singleton creation approaches here: http://www.yoda.arachsys.com/csharp/singleton.html
3: (see 4) 4: no IEnumerator has those members; IEnumerable has GetEnumerator. However, your code doesn't really (seem to) expose the DataTable implementation very much - it just exposes the Folder class; so I would do something like (in pseudo#):
// static field static readonly List<Folder> folders;
// static ctor // after initialising DT folders = new List<Folder>(); foreach(table-row in DT) { folders.Add(new Folder(table-row)); } folders.TrimExcess(); // or possibly use ToArray and just store the Folder[]
// GetEnumerator() return folders.GetEnumerator();
// iterator return folders[index];
etc
I would also implement IEnumerable<Folder>, not IEnumerable and not IEnumerator
5: fair enough 6: context is everything; that seems reasonable
re CMS: I haven't used CMS; this looks to be related to the http-context (such as the current user), so it isn't *purely* static. I'm guessing that they are caching the details per-user in session-state or some other cache. As long as your collection doesn't change per user your approach should be fine.
Marc
David Colliver - 17 Nov 2006 11:30 GMT Thanks Marc,
I will have a go at implementing this.
re: the CMS stuff, I have been able to update the collection sort of dynamically. In this case, it is a list of folders from the database (sort of virtual folders, no file system folders though). Only admins will add new folders. On their page, when a new folder is submitted, I can update the datatable. I wonder if this is why the class may not be thread safe.
You are correct with the http-context. I am still getting used to this. Something else I want to do (which I will learn sometime soon) is getting all the dotted lines, and geting parent items etc. such as...
CmsHTTPContext.Current.Channel.Parent.Parent.Parent.Url
(I need to do things like... 1. Find the current channel. 2. find the parent channel (recursive it needed) 3. find the URL. all 3 items can be independent, which means I can stop at any point in the chain above)
All these will need to be put into my project, but I can refactor my code later when I learn this.
Regards, Dave Colliver. http://www.AshfieldFOCUS.com ~~ http://www.FOCUSPortals.com - Portal franchises available
> 1. Actually I think it is IEnumerator that is unnecessary / confusing > matters [quoted text clipped - 43 lines] > > Marc Marc Gravell - 17 Nov 2006 12:11 GMT My original "thread safe" comment related to the fact that if 2 callers invoked the static getter at the same time, and both get past the ==null check, then you could theoretically get two instances of the singleton. Very unlikely, but technically possible. In reality you would have to push hard to invoke this, as it only ever changes once per app-domain.
However: if you are allowing people to insert into the DT, then you have a whole other set of problems... enumerators are usually not fans of the data changing, so if an admin adds a folder to your static object while another user is enumerating (foreach), then the latter will burn. You would generallly want to protect multiple adds using a lock. Depending the scenario, a ReaderWriterLock may be in order here (if writes are very rare, and you want simultaneous reads), but to avoid the problem above you would have to lock the collection (read lock) every time you enumerate it.
Rather than expect the client to do this work, another option is to provide a method that does the readlock, pushes the data into an array (.ToArray() using List<T>), and returns the array as a standalone snapshot - this is then uaffected by other threads, and the caller can enumerate it (or use the indexer) at their leisure - i.e.
public static Folder[] GetFolders() { Folder[] result; // TODO: get read lock; see ReaderWriterLock on MSDN2 result = folders.ToArray(); // TODO: release read lock return result; }
public static void AddFolder(Folder folder) { // TODO: get write lock folders.Add(folder); // TODO: release write lock }
With this model, you aslo don't implement IEnumerable or IEnumerable<T>, as the client should enumerate the standalone array, not the more sensitive inner colllection.
Marc
David Colliver - 17 Nov 2006 13:20 GMT Hi Marc,
I am trying to follow you on all this, starting from your first response. My aim is to draw as much knowledge as I can for future use as well, even if it is not in the final implementation of my current aim.
In your first response, you have told me how to set up an indexable folder. After a bit of tinkering, I have this working. I was missing things, but it works now. Thank you.
With your comment about enumerable and enumerator, I followed your next message in the thread and removed IEnumerator from my class definition. I then got an invalid cast. This is on the GetEnumerator function. I have not made any changes (yet) to the cast, as I am not sure what to put here. I have tried changing to IEnumerable and also my ienum to IEnumerable but I get more build errors.
Continuing on... With this failure, I tried to continue with your second message and create the static field List<Folder> but got red squiggly underlines, so could not continue with that as this is also new territory. :-( I did a search and found that it should follow the lines of... List<Folder>SomethingHere folders;
Your // GetEnumerator() is that a function I need to put that in? Is that the GetEnumerator function I refer to earlier?
Again for // iterator???
The two functions in your last message, public static Folder[] GetFolders() and public static void AddFolder(Folder folder)...
Am I to assume that they should be in my Folder class not my Folders class? Also, the last statement, I don't really understand...
Thank you for your time.
Regards, Dave Colliver. http://www.SheffieldFOCUS.com ~~ http://www.FOCUSPortals.com - Portal franchises available
> My original "thread safe" comment related to the fact that if 2 callers > invoked the static getter at the same time, and both get past the ==null [quoted text clipped - 36 lines] > > Marc Marc Gravell - 17 Nov 2006 14:01 GMT What runtime are you using? 1.1? 2.0? List<T> is 2.0 generics, and requires the System.Collections.Generic namespace.
As for the rest of it - my opinion is that you are making all of this a lot more complicated than it needs to be... this would be the entire of my code for this; it allows the caller to get the set of folders (GetFolders()), which they can then enumerate or use the indexer on. It allows an admin to add folders. And it lets it all work at once correctly (ReaderWriterLock).
Marc
using System; using System.Collections.Generic; using System.Threading;
public class Folder { public Folder(object obj) { // TODO: whatever goes here (not shown in your code) } } public static class Folders { private static readonly List<Folder> folders; private static readonly ReaderWriterLock syncLock = new ReaderWriterLock(); static Folders() { folders = new List<Folder>(); //TODO; replace with actual code to initialise from DB foreach (object obj in new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }) { folders.Add(new Folder(obj)); } folders.TrimExcess(); } public static Folder[] GetFolders() { syncLock.AcquireReaderLock(Timeout.Infinite); try { return folders.ToArray(); } finally { syncLock.ReleaseReaderLock(); } } public static void AddFolder(Folder folder) { syncLock.AcquireWriterLock(Timeout.Infinite); try { folders.Add(folder); } finally { syncLock.ReleaseWriterLock(); } } }
David - 17 Nov 2006 19:32 GMT Hi, Sorry, I should have said. I usually do say.
C#, 1.1
I will put your code in and let you know.
Thanks for your help.
Best regards, Dave Colliver. http://www.LeedsFOCUS.com ~~ http://www.FOCUSPortals.com - Local franchises available
> What runtime are you using? 1.1? 2.0? List<T> is 2.0 generics, and > requires the System.Collections.Generic namespace. [quoted text clipped - 57 lines] > } > } David - 18 Nov 2006 11:09 GMT Hi Marc,
(I am writing for ASP.NET)
I have modifed this to use an arraylist as, as you say, List is not part of .NET 1.1 In the Folder class, all that was in there was setting properties from a datarow. Nothing special.
With that, I changed object obj to DataRow dr, though that may cause me a problem. I am not sure yet.
Below is my class. I had to wrap a namespace around the class. I amstill having a few problems with it. (I really do apologise for not having enough knowledge in this area to fix it myself. I have spent hours on it now, got some areas fixed (sort of), but not others. :-( )
Other things I had to fix due to differences in 1.1 and 2, however, not sure if I have fixed them correctly. 1. Can't have a static class, so have removed "static". 2. List<> made to an ArrayList. (Mentioned above) 3. in the folders.Add inside the Folders constructor, I have passed a DataRow. I can't see why this would be any different. 4. folders.TrimToSize() instead of TimeExcess()
If I have made bad assumptions/adjustments, please tell me. All part of the learning process.
The problem at the moment is in the public static Folder[] GetFolders(). I was getting cannot implicitly convert object[] to folders.ToArray(), so I tried a number of things to cast the object, eventually getting (Folder[]) to compile. However, when I run it, I am getting Specified cast is not valid.
public class Folders { //private static readonly List<Folder> folders; private static readonly ArrayList folders; private static readonly ReaderWriterLock syncLock = new ReaderWriterLock();
static Folders() { //folders = new List<Folder>(); folders = new ArrayList(); //TODO; replace with actual code to initialise from DB
DataAccessLayer.DataManager DM = new DataAccessLayer.DataManager(); DataTable DT = DM.FolderList();
//foreach (object obj in new object[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }) foreach (DataRow dr in DT.Rows) { folders.Add(new Folder(dr)); } //folders.TrimExcess(); folders.TrimToSize(); }
public static Folder[] GetFolders() { syncLock.AcquireReaderLock(Timeout.Infinite); try { return (Folder[])folders.ToArray(); } finally { syncLock.ReleaseReaderLock(); } }
public static void AddFolder(Folder folder) { syncLock.AcquireWriterLock(Timeout.Infinite); try { folders.Add(folder); } finally { syncLock.ReleaseWriterLock(); } } }
Something else I am looking at, though not sure yet how it will work... is your AddFolder. The way I think it works is to add a single line. What I would like to do is to re-run my database query and repopulate the folder list from scratch. I can probably work this out for myself though. I will also be wanting to be able to filter the folders based on certain criteria, such as FolderID = ParentFolderID, again, I should be able to work this out.
Something that I am not sure about though as I can't work it out (though am partway there) is how to read it the way I have demonstrated with the Microsoft CMS sample. I am doing it this way...
Folder[] MyFolders = Folders.GetFolders();
foreach (Folder FD in MyFolders) { Response.Write(FD.FolderID.ToString()); Response.Write(FD.FolderName); }
Whilst it is not the way I would have liked, I can live with it. If you know of another way (I know I can change the MyFolders in the foreach to Folders.GetFolders()) I would appreciate the knowledge.
Something else I am not sure yet (as I can't get it to run properly) is that because I have removed the static modifier from the class, will that cause the database to be called each time? This is the main criteria for the class. The other was to not have to use the "new" when instantiating the class.
One final question. This is about the difference between 1.1 and 2. The List<>, what exactly is the difference between List and ArrayList and why use List over ArrayList?
Thanks for your help.
Best regards, Dave Colliver. http://www.ManchesterFOCUS.com ~~ http://www.FOCUSPortals.com - Local franchises available
> What runtime are you using? 1.1? 2.0? List<T> is 2.0 generics, and > requires the System.Collections.Generic namespace. [quoted text clipped - 57 lines] > } > } Marc Gravell - 18 Nov 2006 21:41 GMT > obj to DataRow dr Good call; my "obj" was just a sampl - your code made it clear that there was a ctor, but it wasn't shown, so I "failed safe"
> I really do apologise for not having enough > knowledge in this area to fix it myself No need to; the only way to obtain useful knowledge is to try things, and you are showing that you *are* giving it a good go, so no apology. Sometimes people post what amounts to "please do everything; I haven't even tried"; you have given it fair effort.
> 1. static class It will work identically without it; "static class" is just a handy way of ensuring static semantics at the compiler level, but this can be done with or without this new modifier
> 2. ArrayList, 3. ctor, 4. TrimToSize Perfect
The array cast is unfortunately expected when using an ArrayList. There is an overload to enable this cast: return (Folder[])folders.ToArray(typeof(Folder));
> rerun query etc Fine, but for performance I would run the query first (getting the DT), and then (once complete) take out the write-lock and update the collection (probably Clear() and re-build from scratch)
> foreach question The nuicance is supporing the writer in a threaded environment (such as ASP.NET; *highly* threaded); there are a few tricks, but I think that they may be a little more troublesome long-term; personally, if I only needed the data once (i.e. I don't need to loop 3 times), then I would just use foreach(Folder folder in Folders.GetFolders()) {}.
> List vs ArrayList List<T> (not List) is a generic collection type; this means that it is stronlgy-typed using a template type "T" (that is only decided by the programmer later): List<Folder> knows that it is means to hold Folder items, where-as ArrayList only knows about "object". Firstly this makes it very easy (and safe) to use; as examples, ToArray() returns Folder[], and folders[0] would return a Folder. It also means that you cannot *possibly* add an incorrect typed object (such as a string). For value-types this also avoids boxing etc, making it more efficient.
Marc
David - 19 Nov 2006 13:17 GMT Hi Marc,
I appear at the moment to be taking a lot of knowledge, to the point where you gave me the sample code. I felt a little guilty about this, however it has really helped me along and I have learned one or two things along the way and it is appreciated.
If I was to rerun the query again, you say to take out the Write Lock. How would I do that?
I have overloaded the GetFolders method as I want to be able to just do GetFolders (which would select top level parent), then select specifically child folders of that parent, so that if need be, I can set up a recursive loop. This appears to work excellent, but I would like you to cast your eye over it and tell me if I am missing something, or have made an inefficient mistake.
public static Folder[] GetFolders() { return GetFolders(0); }
public static Folder[] GetFolders(int ParentFolderID) {
DataTable ChildDT = DT.Copy(); // This line took me a while to work. I was doing .Clone() but nothing was happening. DataRow[] RowView = ChildDT.Select("FolderParent=" + ParentFolderID);
ArrayList ChildFolders = new ArrayList();
foreach (DataRow dr in RowView) { ChildFolders.Add(new Folder(dr)); } ChildFolders.TrimToSize();
syncLock.AcquireReaderLock(Timeout.Infinite); try { return (Folder[])ChildFolders.ToArray(typeof(Folder)); } finally { syncLock.ReleaseReaderLock(); } }
The only thing I can think of is that I am populating the arraylist quite early on in the previous code, but with this new method, never getting a chance to read it... Also, do I need to move the syncLock anywhere in the GetFolders overload? In fact, as I am creating a new arraylist that will only be around just for that instance, do I even need the syncLock?
When I am happy with this, then I can do the "ParentFolder" using a similar method.
Would this code be quite easy to do something like...
GetFolders.GetFolders.GetFolders.GetFolders ? (the point being to try and get the Great Grandchildren of the current folder... I have some sample code somewhere to do the multiple dotted thing, so I will see if that can easily be added.)
Once again, thanks for your help.
Best regards, Dave Colliver. http://www.AshfieldFOCUS.com ~~ http://www.FOCUSPortals.com - Local franchises available
>> obj to DataRow dr > Good call; my "obj" was just a sampl - your code made it clear that [quoted text clipped - 42 lines] > > Marc Marc Gravell - 19 Nov 2006 21:43 GMT Actually, the lock should probably be around the .Copy() code; the purpose of this is to ensure that if you have somebody reading the folders while an admin pushes the "update" button, then it all works correctly, i.e. if you have the read lock, it prevents the admin lock being taken until you have finished the Copy(); likewise, if the admin has started work it keeps the readers at bay for a few milliseconds.
I must admin I wasn't entire clear about your dotted parent suggestion - i.e. I couldn't tell enough to comment much.
Re the "parent id" stuff, there may be a few things you could do with hashtable (Dictionary in 2.0), but I would keep it simple for now ;-p
Marc
David Colliver - 17 Nov 2006 09:58 GMT Thank you Marc,
The initial enumerable info I found on the net somewhere, as also the singleton.
I have had quite a battle to get them both to work,
My background is classic ASP, having moved into .net a couple of years ago, but still having large gaps in my knowledge.
I did try something similar to the public Folder this[int rowIndex] but kept getting errors. However, I don't think I had the "this" word. I will try that.
For your points... 1. I assume this is with the two items that the class is derived from. Can I just remove the IEnumerable and it will work? 2. At the moment, thread safety is not an issue, but is likely to be as the project progresses. How should I write it to be safe? 3. (Pass the obtoned enumerator) Can you give me some pointers? code examples or URLs? 4. Again, this was just copied. I understand that an ienumerable has to have a min of 3 items. reset, movenext and current. What should I be doing here? 5. polymorph... This is a direct copy. Having not covered polymorphing, I couldn't really question this, though I can quite clearly see (now you mention it) that it is a cast. 6. I have had a 24 message thread in aspnet trying to acheive this. What I am wanting is a "global" datatable that is enumerated. I want to be able to just simply assign a variable without having the "new" operator. An example...
I wanted to have the folders loaded from the database. I DID NOT want to have to go to the database every time, as a DB call is expensive.
I wanted all my controls, Page, UserControls and Custom Controls to be able to call the same instance of data. Having a "new" operator would instantiate the data each time, thereby calling the database each time. If you are familiar with Microsoft CMS, I want to do something like...
Channels MyChannels = CmsHttpContext.Current.Channel.Channels;
foeach (Channel ch in MyChannels) { Response.Write ch.DisplayName; }
WantedChannel = MyChannels.Channel.GetByGuid("guid-string");
Notice, no "new".
Thank you.
Regards, Dave Colliver. http://www.AshfieldFOCUS.com ~~ http://www.FOCUSPortals.com - Portal franchises available
> OK, one thing at a time. > [quoted text clipped - 36 lines] > > Marc
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|