简体   繁体   中英

C# Base class with static list to be different between instantiated types?

I have a need to create unique ID's for my objects which can be saved and loaded between application instances.

I have this working in my current implementation but it means each of my classes needs almost the same piece of code, therefore I decided to create a base class with this code in and then inherit from that. The code is below. The only issue I'm having is that because I have a static list in the base class, all inherited class types are getting added to the same list.

Therefore how can I change the code so that the List 'items' is a different list between types?

To clarify. If I have two classes list this: Foo: UniqueObject Bar: UniqueObject I want to the Foo and Bar to have their own static item list

abstract class UniqueObject
{
    static protected List<UniqueObject> items = new List<UniqueObject>();
    static Random rnd = new Random();

    int id;

    public int Object_ID { get { return id; } }

    protected UniqueObject()
    {
        id = generateUniqueID();
        items.Add(this);
    }

    protected UniqueObject(int Object_ID)
    {
        // Ensure the passed ID is unique
        if (!isIdUnique(Object_ID))
        {
            // if not it does not get added to the items list and an exception is thrown.
            throw new ArgumentNullException("Object ID is not unique. Already being used by another object.");
        }

        // If we get here then must have been unique so add it.
        items.Add(this);
    }

    /// <summary>
    /// Generates the unique identifier.
    /// </summary>
    /// <returns>The unique ID</returns>
    private int generateUniqueID()
    {
        // get a random number
        int val = rnd.Next();

        // check it is unique, if not get another and try again.
        while (!isIdUnique(val))
        {
            val = rnd.Next();
        }

        return val;
    }

    /// <summary>
    /// Checks that the passed ID is unique against the other
    /// objects in the 'items' list.
    /// </summary>
    /// <param name="ID">The identifier.</param>
    /// <returns></returns>
    private bool isIdUnique(int ID)
    {
        foreach (UniqueObject c in items)
        {
            if (c.id == ID)
                return false;
        }
        return true;
    }
}

I believe I can achieve this using Generics so i could change the class and list to something like this:

abstract class UniqueObject<T>
{
    static protected List<T> items = new List<T>();

But this gives other errors with the line items.Add(this).

Any help would be appriciated.

If you want an unique id that has the folowing properties:

1) Is unique in current app domain

2) Values are unique even when dealing with multiple instances of your application.

Then you need to consider one of these solutions:

1) Generate GUIDS

2) Have a unique "server" for your generated ids (a common server that can serve your ids)

3) If you know exactly how many application instances you have, you can define a "series" of unique ids for each instance.

And last, you need to abstract the notion of unicity into a separate service that you can move around in whatever tier / layer of your application. Your objects must NOT contain logic about unicity, this notion is a separate concern and you must be deal with it in other component. Please apply the separation of concerns pattern.

So this is my implementation (if I would be you)

public interface IEntityWithUniqueId
{
    void SetUniqueId(string uniqueId);

    string UniqueId { get; }
}

public interface IUniqueIdsProvider
{
    string GetNewId();
}

public class UniqueObject : IEntityWithUniqueId
{
    public string UniqueId { get; private set; }

    void IEntityWithUniqueId.SetUniqueId(string uniqueId)
    {
        UniqueId = uniqueId;
    }
}

public class MyObjects : UniqueObject
{

}

public class RemoteUniqueIdsProvider : IUniqueIdsProvider
{
    public string GetNewId()
    {
        // calling a service ...., grab an unique ID
        return Guid.NewGuid().ToString().Replace ("-", "");
    }
}

public class UniqueObjectsFactory<T> where T : IEntityWithUniqueId, new ()
{
    private IUniqueIdsProvider _uniqueIdsProvider;
    public UniqueObjectsFactory(IUniqueIdsProvider uniqueIdsProvider)
    {
        _uniqueIdsProvider = uniqueIdsProvider;
    }

    public T GetNewEntity()
    {
        var x = new T();
        x.SetUniqueId(_uniqueIdsProvider.GetNewId ());

        return x;
    }
}

I wrote a test method like this:

[TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void UniqueObjectTest()
        {
            var provider = new RemoteUniqueIdsProvider();

            var factory = new UniqueObjectsFactory<MyObjects>(provider);

            var entity = factory.GetNewEntity();
            var entity2 = factory.GetNewEntity();

            Assert.AreNotEqual(entity.UniqueId, entity2.UniqueId);


        }

    }

To explain what is above: 1) The interface IEntityWithUniqueId defines how an "unique" object must look like in your application, so it is an object that has an UniqueId property and also a special method: SetUniqueId. I didn't made the property UniqueId with get and set because "set" would be an infrastructure operation but get will be a developer API.

2) The interface IUniqueIdsProvider tells you how a unique ids provider will look like. It must have a simple method: GetNewId (); that serves you an unique Id. The implementation can be anywhere (on a server, locally, etc)

3) UniqueObject class. This class is the base class for all your unique objects.

4) UniqueObjectsFactory. This is the class that serves you new unique objects. When loading objects from disk, you must start from the asumption that you GENERATED unique ids, so when loading them you don't have to deal with checking unicity again.

On your last remark about using generics, I guess you could do this:

abstract class UniqueObject<T> where T : class

And then

items.Add(this as T);

This should work, and this as T should never fail on runtime if you don't explicitly use UniqueObject<> .

I'm not sure about how I feel about advocating having static members on generic types ( and you should not do that ), but this should at least work

Update : yes, it seems to work

Recommendation

In my answer, I tried to answer exactly what you were asking. But with all this said, if all you need is a unique ID for your objects and checking if it's not duplicated when you create them, you could:

  1. Use a GUID, and forget about checking. GUID collisions are theoretically possible.... but will it happen? Under normal conditions, more likely not. Even if you created a trillion GUIDs in a year, there's a higher chance your program will crash by a meteorite striking the computer several times before you find a duplicate

  2. Still, if you want to check it and make absolutely sure (which is a fair thing to do, actually), this could be way easier, and you don't need to store a list of the whole objects per-type to do this... see this simple base class, which will do what you want in a similar way you are doing it:

     abstract class UniqueObject : IDisposable { static protected HashSet<Guid> Guids = new HashSet<Guid>(); Guid _id; public Guid ObjectID { get { return _id; } } protected UniqueObject() { do { _id = Guid.NewGuid(); } while(Guids.Contains(_id)); Guids.Add(_id); } protected UniqueObject(Guid guid) { if(Guids.Contains(guid)) throw new ArgumentNullException("Object ID is not unique. Already being used by another object."); _id = guid; } // Make a better implementation of IDisposable public void Dispose() { guids.Remove(_id); } } 

And that's it. If you still want to use int instead of Guid , you could just change it to int , and have something like:

// static members
static protected Random rnd = new Random();
static protected HashSet<int> ids = new HashSet<int>();
// ... in the constructor:
do
{
  _id = rnd.Next();
} while(ids.Contains(_id));

Which looks similar to what you had before

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM