4

I have a list of permissions defined like this:

private List<PermissionItem> permissionItems;
private ReadOnlyCollection<PermissionItem> permissionItemsReadOnly;

This list is retrieved from a web service via a background thread. The read only version is filled from the List version.

I expose this list to the rest of my (rather large) application like this:

public IQueryable<PermissionItem> PermissionItems
{
   get
   {
       // Make sure that the permissions have returned.  
       // If they have not then we need to wait for that to happen.
       if (!doneLoadingPermissions.WaitOne(10000))
           throw new ApplicationException("Could not load permissions");

       return permissionItemsReadOnly.AsQueryable();
   }
}

This is all well and good. The user can ask for permissions and get them once they have loaded.

But if I have code like this in a constructor (in a different class):

ThisClassInstanceOfThePermisssions = SecurityStuff.PermissionItems;

Then I am fairly sure that will block until the permissions return. But it does not need to block until the permissions are actually used.

I have read that IQueryable is "Lazy Loading". (I have used this feature in my Entity Framework code.)

Is there a way I could change this to allow references to my IQueryable at any time, and only block when the data is actually used?

Note: This is a "nice to have" feature. Actually loading the permissions does not take too long. So if this is a "roll your own" query/expression stuff, then I will probably pass. But I am curious what it takes to make it work.

Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • What behavior do you want for the returned IQueryable? What should happen if the list is not completely loaded? – Thomas Levesque Aug 08 '12 at 21:07
  • @ThomasLevesque - I would prefer to move my `WaitOne` code to somewhere where the data in the list is actually accessed. And allow assigning reference (and maybe creating "sub" IQueryables) to be set up without blocking. – Vaccano Aug 08 '12 at 21:14

2 Answers2

5

Yes this is possible. First, probably you should switch to IEnumerable as you are not using any IQueryable features. Next, you need to implement a new iterator:

public IEnumerable<PermissionItem> PermissionItems
{
   get
   {
        return GetPermissionItems();
   }
}
static IEnumerable<PermissionItem> GetPermissionItems()
{
       // Make sure that the permissions have returned.  
       // If they have not then we need to wait for that to happen.
       if (!doneLoadingPermissions.WaitOne(10000))
           throw new ApplicationException("Could not load permissions");

       foreach (var item in permissionItemsReadOnly) yield return item;
}

The event will only be waited on if the caller of the property enumerates the IEnumerable. Just returning it does nothing.

usr
  • 168,620
  • 35
  • 240
  • 369
  • He says he exposes the list to the rest of the app, not over the wire. Anyway, IQueryable does not magically allow query over the wire, either. – usr Aug 08 '12 at 21:45
  • @usr - This is what I was looking for. Seems I never think of using the `yeild`, but it allows some good stuff! – Vaccano Aug 08 '12 at 21:52
  • 1
    @Vaccano, you can also implement IEnumerable manually but that is an awful task. yield and manual implementations have the same expressive power. – usr Aug 08 '12 at 21:57
  • @Vaccano J.Skeet has got really great stuff on iterators, you can get very detailed insight on how that works. See his answer [here](http://stackoverflow.com/a/317502/706456). Anyways +1 for a helpful answer. – oleksii Aug 08 '12 at 21:59
  • As an oddity, ReSharper wants me to change the last line to `return permissionItemsReadOnly;` Is that the same thing as using the yield? – Vaccano Aug 08 '12 at 22:23
  • No that would make the method execute synchronously. R# is applying a useful heuristic here, but in this case it does damage. Choose "ignore with comment" from the light bulb menu to document your intention to others! – usr Aug 08 '12 at 23:05
1

Take a look at the Lazy<T> class.

Lazy initialization occurs the first time the Lazy.Value property is accessed. Use an instance of Lazy to defer the creation of a large or resource-intensive object or the execution of a resource-intensive task

oleksii
  • 35,458
  • 16
  • 93
  • 163