3

I'm trying to figure out what the proper syntax is to achieve a certain API goal, however I am struggling with visibility.

I want to be able to access a Messenger instance's member like msgr.Title.ForSuccesses.

However, I do not want to be able to instantiate Messenger.Titles from outside my Messenger class.

I'm also open to making Messenger.Titles a struct.

I'm guessing I need some sort of factory pattern or something, but I really have no idea how I'd go about doing that.

See below:

class Program {
    static void Main(string[] args) {
        var m = new Messenger { Title = { ForErrors = "An unexpected error occurred ..." } }; // this should be allowed
        var t = new Messenger.Titles(); // this should NOT be allowed
    }
}

public class Messenger {
    // I've tried making this private/protected/internal...
    public class Titles {
        public string ForSuccesses { get; set; }
        public string ForNotifications { get; set; }
        public string ForWarnings { get; set; }
        public string ForErrors { get; set; }

        // I've tried making this private/protected/internal as well...
        public Titles() {}
    }

    public Titles Title { get; private set; }
    public Messenger() {
        Title = new Titles();
    }
}
Olson.dev
  • 1,766
  • 2
  • 19
  • 39
  • 1
    Your current setup seems fine. No one can assign `Titles` as you have a `private set`. So what seems to be the issue? – Mrchief Jun 30 '11 at 18:21
  • Strongly advise against a `struct` if `Messenger`: contains reference types; is not intended to be immutable; or does not have a short-lived instantiation. I would stick with the reference type. – IAbstract Jun 30 '11 at 18:21

4 Answers4

7

You just need to make Titles private and expose an interface instead of it.


class Program {
    static void Main(string[] args) {
        var m = new Messenger { Title = { ForErrors = "An unexpected error occurred ..." } }; // this is allowed
        var t = new Messenger.Titles(); // this is NOT allowed
    }
}

public class Messenger {
    public interface ITitles {
        string ForSuccesses { get; set; }
        string ForNotifications { get; set; }
        string ForWarnings { get; set; }
        string ForErrors { get; set; }
    }

    private class Titles : ITitles {
        public string ForSuccesses { get; set; }
        public string ForNotifications { get; set; }
        public string ForWarnings { get; set; }
        public string ForErrors { get; set; }
    }

    public ITitles Title { get; private set; }
    public Messenger() {
        Title = new Titles();
    }
}

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
  • But now I have `Messenger.ITitles` exposed - it feels dirty that I _could_ implement `Messenger.ITitles` in another class/interface when all I wanted to do was keep my scope clean (instead of just `msgr.TitleForSuccess`). Or am I the only one on that boat? – Olson.dev Jun 30 '11 at 18:32
  • @Olson.dev: well, IMHO, it's just too paranoid to worry about implementing ITitles. Actually, I wouldn't ever bother to prohibit Titles class creation. What scenario are you trying to avoid in the first place? – Konstantin Oznobihin Jun 30 '11 at 18:38
  • @Konstantin Oznobihin: I suppose, namely, for someone to think using `Messenger.I?Titles` has any real purpose outside of my implementation/logical grouping. Also, I'd prefer to keep IntelliSense clean... not that it's a big deal. I just know that, personally, I don't like typing "." and then being overwhelmed with things that may or may not be useful to me. – Olson.dev Jun 30 '11 at 18:49
  • 1
    @Olson.dev:if you are concerned with IntelliSense, then you could use EditorBrowsable attribute to hide such implementation details (although, it'd work only for other projects referencing your assembly). As for somebody who'd think of using ITitles, you could just add documentation comment telling it's not intended to be used by anyone. – Konstantin Oznobihin Jun 30 '11 at 18:53
  • 1
    @Olson.dev: if you are still concerned with this, you could take your original code, make Titles ctor private and create it in Messenger using Activator.CreateInstance. Somewhat hacky, but simple and does the job. – Konstantin Oznobihin Jun 30 '11 at 18:59
  • @Konstantin Oznobihin: Fair enough. I +1'd your comment -- I'll just leave my implementation as-is, add that attribute and a documentation comment. Thanks for the insight. edit: I +1'd that comment as well. I'll try both approaches when I get home -- see which one I like more. They both will suffice. – Olson.dev Jun 30 '11 at 19:00
  • +1. Exactly what I was going to post. The interface can even restrict the exposed properties (e.g., the concrete implementation can expose a public get/set property whilst the interface only exposes a public get property.) – Nicholas Carey Jun 30 '11 at 19:35
  • @Nicholas: Not in this case, we still need setters in interface to support object initialization. – Konstantin Oznobihin Jun 30 '11 at 19:41
  • I realize that: I'm just pointing out your options. The pedant in me says that object state shouldn't be directly changeable by an external agent: object state should change as a result of a logical operation on the object (asking the object to do something useful in its problem domain). – Nicholas Carey Jun 30 '11 at 19:46
0

If you make the Titles constructor internal you will be able to create instances of it within your assembly only. If it is an API, perhaps that will be protected enough? You can see this pattern within the BCL (such as HttpWebRequest that can be created only through calls to WebRequest.Create).

Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
0

Why Would I Ever Need to Use C# Nested Classes Nested type is never intended to be initialized from external type.

Community
  • 1
  • 1
hungryMind
  • 6,931
  • 4
  • 29
  • 45
0

Well, you could make Titles a struct and make the constructor either public or internal. In that way, every time a client gets a copy of the Titles instance through the Title property, they will be getting the value, not the reference. They could modify that value, but to apply that change to the internal state of your object, they would need to be able to set the value back again through the Title property. They can't, because you have the Title setter marked private.

You will have to do the same when you change a value internally. For example:

// Your constructor...
public Messenger()
{
    Titles t = new Titles();
    t.ForSuccesses = "blah";
    Title = t;
}

You can do this internally because you have access to the private setter for the Title property.

The main downside is that it might confuse the clients of your framework a bit because it looks like you can set the values of the Titles instance, but there is no real way for them to commit that change back to the Messenger class.

Jonathan DeCarlo
  • 2,798
  • 1
  • 20
  • 24