开发者

How to emulate "friend" in C#?

I need to implement a little bit of C++ style 'friend' functionality in C# and am looking for ideas.

Consider two classes that are very closely related: Node and NodePart. Nodes contain NodeParts which are added via public AddPart() calls from other systems with parts that those systems construct. Node needs to "reach into" NodePart to do some very specific notifications that NodePart will distribute via separate virtual protected methods to any derived classes after some processing. (For anyone familiar with component-based game object programming, this is the same kind of thing.)

I'd like to be able to have NodePart give Node a way to get at these few notification methods without letting any other types in the system do it. It's not necessary for Node to access any other NodePart internals, just forward along some private notifications.

Now, putting these classes in an assembly and using 'internal' would obviously do the trick, but I'm looking for a better pattern than that. (I'm not really interested in spawning new assemblies for every set of classes I'd like to do this with in the future.)

Aside from reflection + invoke, which is yucky and brittle, what other patterns can you think of to solve this problem here? My spidey senses tell me that interfaces are part of the solution, but I can't think of how.

(Note: I'm ok with security through obscurity. This system doesn't have to be 100% proof against misuse - just enough to discourage people from doing what they should not. We're not building defibrillators here.)

Update: lots of the below answers require multiple assemblies. As I mention above, I'd really like avoid this. I do not want to put one system per assembly. We have enough of the things as is, and I can't go down the IL linking route due to our use of XAML. But thanks for the answers anyway. :)

Update 2: I messed around in Linqpad and came up with a few options based on the answers below. Which do you like worst/least and why?

Option 1: Obsolete

#pragma warning disable 612 // doc this
public sealed class Node : NodePart.SystemAccess {
#pragma warning restore 612
    NodePart _part;

    public NodePart Part {
        get { return _part; }
        set { _part = value; NotifyAdded(_part); }
    }
}

public class NodePart {
    void NotifyAdded() { Console.WriteLine("Part added"); }

    [Obsolete] public class SystemAccess // doc this
    {
        protected void NotifyAdded(NodePart part) { part.NotifyAdded(); }
    }
}

Not bad. A little weird but the weirdness is very confined. I'm leaning towards this one because it's so much more compact than the next option.

Option 2: Access + Hack

public sealed class Node {
    static readonly NodePart.ISystemAccess _systemAccess;

    static Node() {
        _systemAccess = (NodePart.ISystemAccess)typeof(NodePart)
            .GetNestedTypes(BindingFlags.NonPublic)
            .Single(t => t.Name == "SystemAccess")开发者_开发百科
            .GetConstructor(Type.EmptyTypes)
            .Invoke(null);
    }

    NodePart _part;

    public NodePart Part {
        get { return _part; }
        set { _part = value; _systemAccess.NotifyAdded(_part); }
    }
}

public class NodePart {
    void NotifyAdded() { Console.WriteLine("Part added"); }

    internal interface ISystemAccess {
        void NotifyAdded(NodePart part);
    }

    class SystemAccess : ISystemAccess {
        void ISystemAccess.NotifyAdded(NodePart part) {
            part.NotifyAdded();
        }
    }
}

Hacka hacka hacka. My original version didn't have the reflect+invoke and would have relied on SystemAccess being non-obvious. That might be ok too but I kind of like the bit of extra security I get here, even though it's probably not necessary.


Make NodePart a nested class in Node, it will be able to access all of its private members.

If that's not possible (don't want a using Node?), then you could try partial classes. I don't know how that would work though, because I've never really used them in a more advanced way like that.


Are friend assemblies what you're looking to do? They've been in the framework since 2.0, but I don't think it's been very well advertised.

Friend assemblies allow you to access internal methods in assembly B from assembly A, but protected and private members are still hidden.

Not sure if this will help, but thought it may.

Oh, and the one drawback is that you have to strong name those assemblies.


The only way I can think of using interfaces is to implement the fields of NodePart explicitly, which would require any derived classes to be downcast to the interface if they wish to access them.

interface INodePart {
    T SomeValue { get; }
}

class NodePart : INodePart {
    T INodePart.SomeValue { get; private set; }
}

class Node {
    void AddNodePart(NodePart np) {
        T val = (np as INodePart).SomeValue; //require downcast here.
        ...
    }
}

Note that you need to downcast even inside NodePart if you wish to access Somevalue.

This obviously isn't fool proof - anyone can downcast if they want, but it's certainly a discouragement.

To add to the discouragement, you could mark the interface with the [Obsolete] attribute, and wrap the NodePart and Node classes inside a disabled warning block which will mean anyone else attempting to use the INodePart will be given a warning, but your code will not.

#pragma warning disable 612
class NodePart : INodePart { ... }
#pragma warning restore 612

You could get strict on this, and add /warnaserror:612 to your build arguments, but it'll break your code if you rely on any other code marked Obsolete elsewhere. (Although you can enable/disable this for certain files, but will probably require manually hacking the .csproj file)


InternalsVisibleto is the closest you will get to "Friend" in C#

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.internalsvisibletoattribute.aspx


I believe you can actually make an Interface internal, so the behaviour you need could be encapsulated in the main assembly of your project, but anything else wouldn't see the behaviour ?


You might, if you really wanted to, be able do this using attributes such that the friend classes store a cryptographic hash of whatever the "Friended" class can access of the friend class's code at run time using reflection, and have a public version of the method that checks this hash each time it's called. I haven't tried this, and am not sure reflection gives you anything suitable to hash against, like the binary bits of the friend class.

Or you might be able to use the .NET security in a similar way.

Just brainstorming, these wouldn't be worth it if you can avoid it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜