views:

153

answers:

4

Okay I have a series of objects based on a base class which are stored randomly in a Dictionary object. e.g.

class TypeA
{
   public int SomeNumber {get; set;}
   public void SomeFunction()
}

class TypeB : TypeA
{
   public string SomeString {get; set;}
}

class TypeC : TypeA
{
   public bool StuffReady()
}


Dictionary listOfClasses <long, TypeA>;

The Key value is a running count of the number of objects that have ever been placed into the dictionary. It does not match the current Dictionary count.

I wish to locate an object of TypeB where its SomeString == "123" say, and remove it. What would be the best way of doing this?

+3  A: 

There doesn't appear to be a mapping between the TypeA class and the Key of the Dictionary. The key is of type long and there is no property of that type on the class. So you'll have to search the collection of KeyValuePair instances which will give you direct access to the key.

Try the following

var found = listOfClasses
  .Where(p => p.Value.SomeString == "123").
  .Where(p => p.Value.GetType() == typeof(TypeB))
  .Single();
listOfClasses.Remove(found.Key);
JaredPar
Thios requires a cast to access property SomeString before compilation can pass, but an exception is then thrown at run time - something to do with IEnumerable and KeyValue pairs. Still it set me thinking so +1.
ChrisBD
+5  A: 

If you're sure that there will only be a single matching object (or if you only want to remove the first occurrence):

var found = listOfClasses.FirstOrDefault
    (
        x => (x.Value is TypeB) && (((TypeB)x.Value).SomeString == "123")
    );

if (found.Value != null)
{
    listOfClasses.Remove(found.Key);
}

If there could be multiple matching objects and you want to remove them all:

var query = listOfClasses.Where
    (
        x => (x.Value is TypeB) && (((TypeB)x.Value).SomeString == "123")
    );

// the ToArray() call is required to force eager evaluation of the query
// otherwise the runtime will throw an InvalidOperationException and
// complain that we're trying to modify the collection in mid-enumeration
foreach (var found in query.ToArray())
{
    listOfClasses.Remove(found.Key);
}
LukeH
There should only be one object with a SomeString value of "123". I'll use the same or similar code to ensure this.The compiler wont allow a null check on 'found', but the Remove statement doesn't throw an exception if 'found' has no value.
ChrisBD
@ChrisBD: Good catch. Calling Remove when no items were found might cause problems if there's a legitimate dictionary entry with Key=0 (because the value of found.Key will default to 0 when no item is found). I've updated the code to do the null check on found.Value instead.
LukeH
@Luke. Good point, I wasn't sure whether checking found.Value would be needed, but I concur with your observation on the Key value possibly being 0.
ChrisBD
A: 

You need to store the running length in each object. Add a property say RunningLength in TypeA. Then use two dictionaries one to store TypeA objects and other for TypeB objects.

Whenever you create a object (of any type descending from TypeA) add it to first dictionary. If the object created is of TypeB add it to the second dictionary:

Dictionary listOfClasses <long, TypeA>;
Dictionary listOfTypeBClasses <string, TypeB>;

TypeA newOject = TypeAFactory.Create();

if(newObject is TypeB)
{
  TypeB objB = newObj as TypeB;
  listOfTypeBClasses[objB.SomeString] = objB;
}

To remove a object of TypeB with SomeString = "123"

if(listOfTypeBClasses.ContainsKey("123"))
{
  long keyA = listOfTypeBClasses["123"].RunningLength;
  listOfClasses.Remove(keyA);
  // if required remove the item from listOfTypeBClasses as well
  listOfTypeBClasses.Remove("123");
}
Vivek
A: 

If you are looking in terms of performance then for loop is better way to go but if you are looking from compactness of code or from code management point of view then LINQ is the way.

Harryboy
I believe the performance difference would be very, very, very small.
Meta-Knight
In this case I suspect that there'll be very little performance difference between a LINQ query and a "foreach" loop. (As always, if performance is that much of a concern then you should profile/benchmark.)
LukeH