internal member in an interface

2020-08-09 04:48发布

问题:

I have a list of objects implementing an interface, and a list of that interface:

public interface IAM
{
    int ID { get; set; }
    void Save();
}

public class concreteIAM : IAM
{
     public int ID { get; set; }
     internal void Save(){
     //save the object
     }

    //other staff for this particular class
}

public class MyList : List<IAM>
{
    public void Save()
    {
        foreach (IAM iam in this)
        {
            iam.Save();
        }
    }

    //other staff for this particular class
}

The previous code doesn't compile because the compiler requires all the interface members to be public.

internal void Save(){

But i don't want to allow the from outside my DLL to save the ConcreteIAM, it only should be saved through the MyList.

Any way to do this?

Update#1: Hi all, thanks for the answers so far, but none of them is exactly what i need:

The interface needs to be public because it is the signature the client from outside the dll will use, along with ID and other properties i didn't bother to write in the example to keep it simple.

Andrew, I don't think the solution is create a factory to create another object that will contain the IAM members + Save. I am still thinking... Any other ideas?

回答1:

I think you don't understand what an interface is for. Interface is a contract. It specifies that an object behaves in a certain way. If an object implements an interface, it means that you can rely on it that it has all the interface's methods implemented.

Now, consider what would happen if there was an interface like you're asking for - public, but with one internal member. What would that mean? An external object could implement only the public methods, but not the internal one. When you would get such an external object, you would be able to call only the public methods, but not the internal one, because the object couldn't implement it. In other words - the contract would not be fulfilled. Not all of the methods would be implemented.

I think that the solution in this case is to split your interface in two. One interface would be public, and that's what your external objects would implement. The other interface would be internal and would contain your Save() and other internal methods. Perhaps this second interface could even inherit from the first. Your own internal objects would then implement both interfaces. This way you could even distinguish between external objects (ones that don't have the internal interface) and internal objects.



回答2:

Make another interface that is internal, and use explicit implementation for the method.

internal interface InternalIAM
{
    void Save();
}

public class concreteIAM : InternalIAM
{
    void InternalIAM.Save()
    {
    }
}


回答3:

I don't think you should be using a interface here maybe you should be using an abstract base something like.:

public abstract class AM
{
    public int ID { get; set; }
    internal abstract void Save();
}

public class concreteIAM : AM
{
    internal override void Save()
    {
        //Do some save stuff
    }
}

Will still allow you to do this:

public class AMList : List<AM>
{
    public void SaveItems()
    {
        foreach (var item in this)
        {
            item.Save();
        }
    }
}


回答4:

I think the best thing to do would be to break the internal and public members into two separate interfaces. If you inherit the interfaces you can still declare the members publicly but the visibility of the internal members will be dictated by the visibility of the interface itself.

using System;

public interface IPublic
{
    void Public();
}

internal interface IInternal : IPublic
{
    void Internal();
}

public class Concrete : IInternal
{
    public void Internal() { }

    public void Public() { }
}


回答5:

It is exactly what I am talking about in my article Friends and internal interface members at no cost with coding to interfaces.

The essence from the article

public class Internal
{
    internal Internal() { }
}

public interface IAM
{
    int ID { get; set; }
    void Save(Internal access);
}

From now one only your assembly can call the Save() method, since an instance of the Internal class can only be created by your assembly.



回答6:

If you don't want external callers to be able to call your Save() method, why not make the whole concreteIAM class internal?

Or, if you want the class public, but not the interface, make the whole interface internal. I think an internal interface can be added to a public class (but I haven't tried it...)



回答7:

Maybe you want to separate the saving of your items into a different set of classes that are internal to your assembly:

internal interface IAMSaver { void Save(IAM item); }

internal class AMSaverFactory {
  IAMSaver GetSaver(Type itemType) { ... }
}

public class MyList : List<IAM>
{
  public void Save()
  {
    foreach (IAM itemin this)
    {
      IAMSaver saver = SaverFactory.GetSaver(item.GetType());
      saver.Save(item)
    }
  }
}


回答8:

Interface members are supposed to be public.. anything else and you should be thinking if you need something else. In this case, you

  • want Save to be an interface member
  • want Save to be only allowed via another class called MyList which lies in the same assembly
  • disallow Save from being called externally (from other classes outside the parent assembly)

Keeping design thoughts aside, you could do this via Explicit Interface Implementation.

    internal interface IPersist
    {
        void Save();
    }
    public class Concrete : IPersist
    {
        void IPersist.Save()
        {
            Console.WriteLine("Yeah!");
        }
    }

// Mylist.cs in the same assembly can still call save like
public void SaveItems()
    {
        foreach (IPersist item in this)
        {
            item.Save();
        }
    }

IPersist is internal, not available outside the parent assembly and since it is explicitly implemented, it cannot be called without an IPersist reference.

new Concrete().Save();     // doesn't compile. 'Concrete' does not contain a definition for 'Save'

Update From your latest response, we have more constraints. The Interface must be public, it should contain the Save method, which shouldn't be publicly available. I'd say go back to the drawing board... something doesn't seem right. IMHO You can't do this with a single interface. How about splitting into 2 interfaces, the public one and the internal one?



回答9:

Why don't you use inner classes to control accessibility of your methods?

Example:

Your primary assembly

public abstract class Item
{
    public int ID { get; set; }
    protected abstract void Save();

    public class ItemCollection : List<Item>
    {
        public void Save()
        {
            foreach (Item item in this) item.Save();
        }
    }
}

Your secondary assembly

public sealed class NiceItem : Item
{
    protected override void Save()
    {
        // do something
    }
}

This pattern will still let you implement Save() method in other assemblies but only ItemCollection which is inner class of Item can call it. Brilliant, isn't it?



回答10:

Go with two interfaces:

public interface IAM
{
        int ID { get; set; }
}


internal interface IAMSavable
{
        void Save();
}

public class concreteIAM : IAM, IAMSavable
{
         public int ID{get;set;}
         public void IAMSavable.Save(){
         //save the object
         }

        //other staff for this particular class
}

public class MyList : List<IAM>
{
        public void Save()
        {
                foreach (IAM iam in this)
                {
                        ((IAMSavable)iam).Save();
                }
        }

        //other staff for this particular class
}

The implementation of Save() must be explicit to prevent clients calling it.



回答11:

I had similar situation and thought this could help. (not sure if this is not needed at this time or not)

This is a legit case for me: in case suppose there is an interface which has some API which is internal to the DLL and some API can be accessed from outside the DLL. Since interface is an abstract class, instead of defining as an interface you can define as a abstract class.

using System.Collections.Generic;

public abstract class IAM
{
    int ID { get; set; }
    internal abstract void Save();
}

public class concreteIAM : IAM
{
    public int ID { get; set; }
    internal override void Save()
    {
        //save the object
    }

    //other staff for this particular class
}

public class MyList : List<IAM>
{
     internal void Save()
    {
        foreach (IAM iam in this)
        {
            iam.Save();
        }
    }

    //other staff for this particular class
}


回答12:

I was wondering on the same issue here, and stumbled upon this question...

As i thought on this, i understood i don't need the internal method in the Interface in the first place.

I can access it through my Concrete Class, and leave the Contract for the Out-Side code.

In your example:

    public interface IAM
    {
            int ID { get; set; }
    }

    public class concreteIAM : IAM
    {
             public int ID{get;set;}
             internal void Save(){
             //save the object
             }

            //other staff for this particular class
    }

    public class MyList : List<IAM>
    {
            public void Save()
            {
                    foreach (concreteIAM iam in this)
                    {
                            iam.Save();
                    }
            }
            //other staff for this particular class
    }