22

I need a function like this:

AddToList(txtName, timeExpire);

It looks like self-descriptive, the item will automatically expire and removed from the list.

I couldn't imagine how to do that. Can anyone give me a clue?

Suman Banerjee
  • 1,923
  • 4
  • 24
  • 40
Nime Cloud
  • 6,162
  • 14
  • 43
  • 75

5 Answers5

26

If you're targeting .NET 4 or above, use the MemoryCache (or derive your custom implementation from ObjectCache).

Back before 4, if you wanted a cache in your app you could simply snag System.Web.Cache and use that in any .NET app. It didn't have any real dependencies on any "web" code, but it did require you reference System.Web.dll. It seemed odd (like referencing Microsoft.VisualBasic.dll from a C# app), but didn't actually affect your application negatively.

With the advent of the Client Profile, breaking dependencies between desktop-centric code and server-centric code became important. So the ObjectCache was introduced so that desktop app (not sure about silverlight, wp7) developers could break that dependency and be able to target just the client profile.

Jesse
  • 8,605
  • 7
  • 47
  • 57
  • 1
    There's a huge red "caution" that says "DO NOT CREATE MemoryCache INSTANCES" here https://learn.microsoft.com/en-us/dotnet/api/system.runtime.caching.memorycache.-ctor?view=netframework-4.8#System_Runtime_Caching_MemoryCache__ctor_System_String_System_Collections_Specialized_NameValueCollection_ Also, note that MemoryCache is not iteratable (there's another caution in GetEnumerator method docs) – Alex from Jitbit May 29 '19 at 16:34
16

Just a quick example for the answer Will gave

Add reference to System.Runtime.Caching

 MemoryCache cache = new MemoryCache("CacheName");

 cache.Add(key, value, new CacheItemPolicy() 
                           { AbsoluteExpiration = DateTime.UtcNow.AddSeconds(20) });

You can then check if something is still in the cache by going

 if (cache.Contains(key))

And if you want your cache to have a low time limit then you will need to change the refresh time of the cache with a nice little hack.

MemoryCache AbsoluteExpiration acting strange

Community
  • 1
  • 1
Adam
  • 16,089
  • 6
  • 66
  • 109
  • do you get a concurrent memory cache? Basically C# `ConcurrentDictionary` with expire functionality? – Zapnologica Mar 11 '16 at 08:04
  • 2
    @Zapnologica MemoryCache is entirely threadsafe, see this answer: http://stackoverflow.com/a/6738179/606554 – elbweb Nov 28 '16 at 14:49
  • 1
    How does `MemoryCache` remove old items? If I want to use this in `web-api`. Does it have a state-full timer or something in the background? Or is it safe to just have a static or singleton instance of the object and access it in each request. – Zapnologica May 14 '18 at 14:42
10

you can also try something like this.

Create a custom class

 public class Custom
    {
        string item; //will hold the item
        Timer timer; //will hanlde the expiry
        List<Custom> refofMainList; //will be used to remove the item once it is expired

        public Custom(string yourItem, int milisec, List<Custom> refOfList)
        {
            refofMainList = refOfList;
            item = yourItem;
            timer = new Timer (milisec);
            timer.Elapsed += new ElapsedEventHandler(Elapsed_Event);
            timer.Start();
        }

        private void Elapsed_Event(object sender, ElapsedEventArgs e)
        {
            timer.Elapsed -= new ElapsedEventHandler(Elapsed_Event);
            refofMainList.Remove(this);

        }
    }

Now you can create List<Custom> and use it. It's items will be deleted once specified time is over.

Haris Hasan
  • 29,856
  • 10
  • 92
  • 122
  • A similar solution worked great for me. In my case, I created a singleton to hold the cached values in a ConncurrentDictionary. Then I use the time event to clear the cached values every so often. I used a sliding expiration. – John C Aug 21 '13 at 17:31
  • 2
    how would this approach work in an asp.net api running in IIS. I believe there are complications with timers running in iis? – Zapnologica Mar 11 '16 at 08:01
  • 2
    Although the answer doesn't say so directly, the `ElapsedEventHandler` reveals that this is using `System.Timers.Timer` which runs the callback on a different thread. That's a huge problem for this answer, because `List` is not threadsafe. – Ben Voigt May 02 '18 at 02:29
3

You will need to create an object that can manage this.

Think about what it will need to store per item. Basically, the data (e.g. txtName), and an expiry time. That means you probably will need a class or struct that just has those 2 elements.

Your ExpiringList class will be a list of that type.

Now you have your basic data structure (a collection/list of ExpiringListItem) you need to think about what operations you want to support. Currently you've listed AddToList, others would probably be RemoveFromList, Clear, and maybe iterating over all items.

The only thing unique to your ExpiringList compared to a normal List<T> is that you want to automatically remove expired items. Therefore it would make sense to make your ExpiringList implement the interface IList<T> and use a private List internally, possibly you could make your list (and by necessity your ExpiredListItem class) generic.

The tricky part, beyond understanding and implementing inheritance (a separate question really), is working out how to remove expired items.

This would mean creating a method within your class that iterated (in reverse order) over your items, comparing their expiry time with the current time and removing expired ones.

You could implement the interface simply by calling the appropriate methods on your internal list.

You could say at this point, you're done, since you just call that method periodically to ensure sure all expired items are gone before you use the items in the list.

Maybe a better option would be to call this method before running the method on the internal list. Depending on how you will use your list this may be overkill.

George Duckett
  • 31,770
  • 9
  • 95
  • 162
  • 1
    In short; I'll use a timer to check expired items periodically. – Nime Cloud Jun 27 '11 at 13:14
  • @Nime, that would work, or you could use the suggestion in my last paragraph (if you did want to make a class that managed this, rather than a single method that works on a list). – George Duckett Jun 27 '11 at 13:15
0

@haris-hasan's solution is great, but this will be a bit costlier; at least for smaller collections. This can be achieved by writing a self managed class as below:

    private class Candidates
    {
        private List<Collection> _lstCandidates = new List<Collection>();

        /// <summary>
        /// Inserts new Candidate into internal collection.
        /// </summary>
        /// <param name="strName">Name of the candidate</param>
        /// <param name="dtExpiry">Expiry date when the entry will be kicked out</param>
        public void Add(string strName, DateTime dtExpiry)
        {
            //To avoid duplicacy, this can be cahnged according to requirement.
            if(_lstCandidates.Find(x=> x.CandidateName == strName) == null){
                _lstCandidates.Add(new Collection(strName, dtExpiry));
            }
            else {
                //Check expiry time and remove
            }
        }

        /// <summary>
        /// Returns candidate if Candidate name has not been expired yet.
        /// </summary>
        /// <param name="strName">Name of the candidate that was previously added into the collection</param>
        /// <returns></returns>
        public string Get(string strName)
        {
            //Find Candidate
            Collection collection = _lstCandidates.Find(x => x.CandidateName == strName);
            if(collection != null)
            {
                //If found then check expiry.
                if(DateTime.Now.Subtract(collection.ExpiryDate).TotalSeconds >= 0)
                {
                    //If expired then remove candidate and return null.
                    _lstCandidates.Remove(collection);
                    return null;
                }
                else
                {
                    //return candidate name
                    return collection.CandidateName;
                }
            }
            else
            {
                return null;
            }
        }
        private class Collection
        {
            public string CandidateName { get; set; }
            public DateTime ExpiryDate { get; set; }
            public Collection(string CandidateName, DateTime ExpiryDate)
            {
                this.CandidateName = CandidateName;
                this.ExpiryDate = ExpiryDate;
            }
        }
    }

For smaller collections this is enough and will return only those entries that have not been expired yet. For larger collections this can be enhanced:

  1. By adding internal timer as suggested by @haris-hasan. This will definitely make it as an independent object.
  2. To save a "timer", a method can be added to kick out expired entries and that method can be called from any existing timer, on page load or before any api-call so that collection can be shrinked as soon as possible.