0

I know there's a pattern involving partial classes to implement a class that has settable properties to the assembly, but is read-only from the outside, something like

// This is just the non-working example to get the idea of what I want it to do across

public interface IMyReadable
{
    int Property1 { get; }
    string Property2 { get; }
}

internal interface IMyEditable : IMyReadable
{
    int Property1 { get; set; }
    string Property2 { get; set;}
}

public class Implementation : IMyEditable
{
    public int Property1 { get; internal set; }
    public string Property2 { get; internal set;}
}

since you can't define 'internal set' in an interface. It can be implemented for classes as e.g. https://stackoverflow.com/a/42082500/6155053

…but what if I want to do this for structs?

I know, structs should be completely read-only usually, but it would be nice to have multiple different struct implementations implementing the interface, and then being able to instance them first and init them property by property afterwards from inside the assembly, e.g. in a factory class, without having to write the setting for each of the interface's properties out for each different implementation (via their c'tors or something).

That would allow me to do something like

// non-sense, but explanatory code snippet
[…]
var structs = new HashSet<IMyEditable>();
structs.Add(new MyEditableImplementation1());
structs.Add(new MyEditableImplementation2());
structs.Add(new MyEditableImplementation3());

int i = 0;
foreach(IMyEditable s in structs)
{
    s.Property1 = ++i;
    s.Property2 = i.ToString();
}

// ...and still be able to set custom properties only some of the implementations
// have afterwards, etc., and when you then return, for example do:

return structs.Cast<IMyReadable>();

Would save writing a lot of boilerplate for initialization as the number of implementations of the interface increases.

However, since structs are always sealed and can't inherit from a "base struct"… how would I do this without losing the advantage of auto-implemented properties (which I would if I'd do it with proxy properties with shared private fields inside every struct—that, again, means a lot of boilerplate code, only in another place).

Is this possible? If so, how?

Edit: Added 2nd sample to explain desired usage

Community
  • 1
  • 1
Johann Studanski
  • 1,023
  • 12
  • 19
  • 2
    _"it would be nice to have multiple different struct implementations implementing the interface, and then being able to instance them first and init them property by property afterwards"_, no, it wouldn't. – hyankov Feb 08 '17 at 11:41
  • Also, I am not sure your sample about the classes will compile. The interface says that the property has public setter, then you try to make it less visible with 'internal'. Not sure that works. – hyankov Feb 08 '17 at 11:44
  • @HristoYankov That's why there's the comment above "This is just the non-working example to get the idea of what I want it to do across" ;) – Johann Studanski Feb 08 '17 at 12:03
  • You could use `Proxy` pattern – hyankov Feb 08 '17 at 12:13
  • @HristoYankov Okay, but how? At least in the examples I found, I couldn't see how I'd implement it in a way that this would help me type less boilerplate… Maybe you could add a (pseudo code?) sample in an answer (as opposed to the comments here on StackOverflow which butcher any code one adds)? That would be super helpful! – Johann Studanski Feb 08 '17 at 12:48
  • 1
    Considering that, in this situation, you're boxing your structs 100% of the time, there is no reason at all for them to be a `struct`. This should be a class, no question. – Servy Feb 08 '17 at 14:30

1 Answers1

-1

How about this?

enter image description here

IReader.cs (ClassLibrary1)

namespace ClassLibrary1
{
    public interface IReader
    {
        int Property1 { get; }
        string Property2 { get; }
    }
}

IWriter.cs (ClassLibrary1)

namespace ClassLibrary1
{
    internal interface IWriter : IReader
    {
        new int Property1 { get; set; }
        new string Property2 { get; set; }
    }
}

SomeObject.cs (ClassLibrary1)

namespace ClassLibrary1
{
    internal class SomeObject : IWriter
    {
        public int Property1 { get; set; }
        public string Property2 { get; set; }
    }
}

Worker.cs (ClassLibrary1)

namespace ClassLibrary1
{
    using System.Collections.Generic;
    using System.Linq;

    public static class Worker
    {
        // NOTE: Thanks to internal on the IWriter, you can't return IWriter here.
        public static IEnumerable<IReader> DoSomething()
        {
            var structs = new HashSet<IWriter>();
            structs.Add(new SomeObject());
            structs.Add(new SomeObject());
            structs.Add(new SomeObject());

            int i = 0;
            foreach (IWriter s in structs)
            {
                s.Property1 = ++i;
                s.Property2 = i.ToString();
            }

            return structs.Cast<IReader>();
        }
    }
}

Program.cs (ConsoleApplication3)

namespace ConsoleApplication3
{
    using System.Collections.Generic;
    using System.Linq;
    using ClassLibrary1;

    public class Program
    {
        private static void Main(string[] args)
        {
            // NOTE: You can't cast the structs to IWriter here.
            var structs = Worker.DoSomething();

            // This will not compile:
            // (structs.FirstOrDefault() as IWriter).Property1 = 10
        }
    }
}

Note that:

  • IWriter is internal, which means you can't pass it/use it outside of ClassLibrary1. Perfect, only your 'internals' can work on it and to the outside world you can expose IReader only, which is what we do.
  • In the Console App, you can't cast the object to IWriter. Which is good.

Interestingly, the 'Immediate window' allows me to cast the struct to IWriter, but I think it uses reflection and this is not allowed in compile time, so you are good!

hyankov
  • 4,049
  • 1
  • 29
  • 46
  • Yeah, after the comment thread yesterday I found it allowed that kind over 'override' of properties in interfaces, too. I had had in mind that this would create two different data stores in the class implementing both interfaces - but it doesn't So that made it easy. I don't know who downvoted your answer, btw, or why, or why he left no comment about the reason =/ PS: I hate that 'return' submits comments here – Johann Studanski Feb 09 '17 at 11:26
  • haha, -1 without any comment, on a complete solution. Typical SO. – hyankov Feb 09 '17 at 12:24