C
C#ā€¢15mo ago
joren

ā” Confusion as to what to default to when it comes to accessibility modifiers

So I come from a extensive C++ background, in which we use the keywords: public and private a bit different. They say everything about accessibility but nothing about assemblies and whether we are allowing external assemblies to access our classes, methods and even members. Now to my understanding internal is basically public but within the assembly im working in, meaning that is how I say: this func() method is public and can now be called on its respective object outside of the class. And public is used to say: hey, this is allowed to be called form another assembly and is public within its own assembly too. Now, knowing this, I concluded that best practice, granted we work within one assembly, the internal keyword is the default one should use however I'd love to get some assurance on it. Since even my IDE defaults to public when auto-generation ctors, methods, etc.
// ./Entities/HumanEntity.cs
internal class HumanEntity : BaseEntity
{
internal HumanEntity() { }
internal HumanEntity(string name) { Name = name; }

internal void func() { }
}

// ./Program.cs
HumanEntity humanEntity = new HumanEntity("asdf");
Console.WriteLine(humanEntity.Name);
humanEntity.func();
// ./Entities/HumanEntity.cs
internal class HumanEntity : BaseEntity
{
internal HumanEntity() { }
internal HumanEntity(string name) { Name = name; }

internal void func() { }
}

// ./Program.cs
HumanEntity humanEntity = new HumanEntity("asdf");
Console.WriteLine(humanEntity.Name);
humanEntity.func();
154 Replies
joren
jorenOPā€¢15mo ago
As shown above, it seems, unnatural? Do people go by the ideology of: "Just make it public unless you have a reason it shouldn't be accessible by other assemblies"
Angius
Angiusā€¢15mo ago
Usually, yes, when it comes to classes, interfaces, structs, records, and enums you default to public It's only with methods that you start worrying about accessibility That being said, some people do swear by giving everything the lowest accessibility possible, like internal for classes, interfaces, ..., and enums
joren
jorenOPā€¢15mo ago
Interesting, how come methods are treated more strict in that regard?
Angius
Angiusā€¢15mo ago
Very often you work with just a single project that isn't meant to be used as a library
Pobiega
Pobiegaā€¢15mo ago
It's worth mentioning that a public property in an internal class.... is essentially internal
Angius
Angiusā€¢15mo ago
So accessibility from the outside is whatever But accessibility within the project matters always
joren
jorenOPā€¢15mo ago
a internal class overrides all its methods and members accesible modifiers?
Angius
Angiusā€¢15mo ago
Not really More like, children having higher accessibility than parent makes no sense
amio
amioā€¢15mo ago
if the class itself is "hidden", the accessibility of its members is irrelevant
joren
jorenOPā€¢15mo ago
ah ye
Angius
Angiusā€¢15mo ago
If I can't use a InternalThing class in my project... I can't exactly use InternalThing.MakeStuff()
joren
jorenOPā€¢15mo ago
you'd hope this gives a compile time error or at least, a warning?
amio
amioā€¢15mo ago
it does if you try to use it, sure
joren
jorenOPā€¢15mo ago
a bit too late in my opinion, but I guess it has its reasons
amio
amioā€¢15mo ago
huh?
Angius
Angiusā€¢15mo ago
Wym "too late"? Do you want error messages as soon as you think of doing it or...?
joren
jorenOPā€¢15mo ago
I would want it to warn me that my access modifier makes no sense
amio
amioā€¢15mo ago
how much earlier could it be than something that pops up in your IDE
joren
jorenOPā€¢15mo ago
Yeah, if the class is internal, and my method is public that wouldnt make sense
amio
amioā€¢15mo ago
it does, if that "makes no sense" in a way that is actually logically nonsense
joren
jorenOPā€¢15mo ago
and wouldnt work?
amio
amioā€¢15mo ago
sure it would the method, if it were not public, would not be visible even if you can "see" the class like... I can't imagine this works differently in C++ either
joren
jorenOPā€¢15mo ago
Okay, lets say you have:
internal class A
{
public void func() {}
}
internal class A
{
public void func() {}
}
I assume, granted im in another assembly, I couldnt even make an object of A, so regardless of func() being public it wouldnt matter?
Angius
Angiusā€¢15mo ago
Yeah
Pobiega
Pobiegaā€¢15mo ago
Correct In fact, as far as assembly b is concerned that class doesn't exist
Angius
Angiusā€¢15mo ago
But it matters for this project
joren
jorenOPā€¢15mo ago
yes, but just use internal?
internal class A
{
internal void func() {}
}
internal class A
{
internal void func() {}
}
better practice then no? clarity of assemblies and its made "available" in its own assembly
amio
amioā€¢15mo ago
The point is that each "level" of accessibility builds on the other. You only care about the accessibility of type members if you can see that type to begin with Anything you make public or private (or whatever else) in one type assumes the consumer knows what that type is, in the first place
Pobiega
Pobiegaā€¢15mo ago
Overkill imho. The class is internal, so it's just moot to change the method too
amio
amioā€¢15mo ago
There's no room for contradiction in this, it's a conceptual misunderstanding
joren
jorenOPā€¢15mo ago
mhm, actually, yeah probably is overkill. Considering you typically dont care about other assemblies just marking the class internal (its that by default), is good enough and then you treat public as if its only talking about the current assembly (within a class that's internal of course) I can see the argument in that, so common practice is just to write classes:
// internal by default
class HumanEntity : BaseEntity
{
public HumanEntity() { }
public HumanEntity(string name) { Name = name; }

public void func() { }
}
// internal by default
class HumanEntity : BaseEntity
{
public HumanEntity() { }
public HumanEntity(string name) { Name = name; }

public void func() { }
}
Since, HumanEntity isnt available to any other assembly any way we disregard caring for that
Angius
Angiusā€¢15mo ago
Basically, although people usually prefer explicit over implicit so that internal would be there Just a convention tho
joren
jorenOPā€¢15mo ago
I see, makes sense - I'll just write it too then. Sorry, its a lot to take in from multiple people at the same time. Are you saying I have a misconception somewhere, if so, please could you point it out for me bcs as of right now I am not sure where!
The point is that each "level" of accessibility builds on the other. You only care about the accessibility of type members if you can see that type to begin with
yes, I share that thought now
amio
amioā€¢15mo ago
I took one of the things you said to mean that you think e.g. "internal class {public method}" was inconsistent if that's not what you meant, the mistake is mine
joren
jorenOPā€¢15mo ago
well originally I thought that it'd be better practice since its clarity but since Pobiega and you all mentioned its pretty much overkill! Thanks a ton though everyone, I have a much better view on it all now. Apologies if I missed some replies.
amio
amioā€¢15mo ago
Keeping things as "locked down" as possible might be a good idea, yeah. My point was just that an access modifier applies to that one particular thing (type, method, whatever). If that happens to be e.g. a class, whatever access modifiers you give its methods/members only matter if that class is accessible.
joren
jorenOPā€¢15mo ago
Yeah, makes sense. It seems you dont seem to be the only one to think that way as both ZZZ and Pobiega say the same thing. Seems like I'll be explicit about the internal on a class level, and use public on its methods and members!
Pobiega
Pobiegaā€¢15mo ago
šŸ‘ Im not sure if there is an official convention about it, but thats how 99% of C# code I write/read is
Omnissiah
Omnissiahā€¢15mo ago
we could talk about sealed too
Angius
Angiusā€¢15mo ago
Eh, it's not an accessibility modifier It's just a "you can't inherit from this" modifier
Omnissiah
Omnissiahā€¢15mo ago
yeah i know but it kinda have, potentially, a similar importance, in terms of standard to mantain in the code (i don't know if i expressed that in an understandable way)
Pobiega
Pobiegaā€¢15mo ago
Eh, kinda. I'm part of the "sealed by default" gang, but this discussion wasnt about the idea of internal classes, rather why properties and methods wasnt also specified internal
Omnissiah
Omnissiahā€¢15mo ago
i just thought that an experienced programmer coming to c# would benefit to know at least that this exists as a concept
Pobiega
Pobiegaā€¢15mo ago
Sure, but what says he doesn't? its not an access modifier
joren
jorenOPā€¢15mo ago
its the equivelant of final in C++
Pobiega
Pobiegaā€¢15mo ago
yep
joren
jorenOPā€¢15mo ago
its a great tool, I use it a lot in production code. I dont default to it, but its a way to enforce not inheriting from a class that's intended to be inherited from! @chaos_solo Thank you though, I didnt know sealed was the keyword they used. I figured it'd be final like in C++ and java.
Omnissiah
Omnissiahā€¢15mo ago
you would have found out sooner or later, i guess
joren
jorenOPā€¢15mo ago
Just like with anything, either from people like you or the internet!
Angius
Angiusā€¢15mo ago
I only default to sealed records. They're immutable, might as well lock them down further
Pobiega
Pobiegaā€¢15mo ago
thats interesting records is where I don't default to sealed lol
Angius
Angiusā€¢15mo ago
That, and I mostly used them just for data in/out
Pobiega
Pobiegaā€¢15mo ago
because they are immutable, its not really a big deal if someone extends them šŸ˜„
Angius
Angiusā€¢15mo ago
I'd rather not have my UserDto inherited from
Pobiega
Pobiegaā€¢15mo ago
fair I actually use record inheritance to build certain DTOs like CreateUserDto and ModifyUserDto are the same, except modify adds an ID
Omnissiah
Omnissiahā€¢15mo ago
i was trying to isolate value objects too
Angius
Angiusā€¢15mo ago
But then you need to add a prop to CreateUserDto that ModifyUserDto doesn't need, and... I like my DTOs entirely self-contained
Pobiega
Pobiegaā€¢15mo ago
eh? no?
Angius
Angiusā€¢15mo ago
Even if they repeat themselves
Omnissiah
Omnissiahā€¢15mo ago
you duplicate even enums?
Pobiega
Pobiegaā€¢15mo ago
modify is just create + id if you ever add something to create that doesnt go in modify, then you can copy at that point - but the idea is that they stay the same
Angius
Angiusā€¢15mo ago
Well, sure, but let's say Create also needs a CreatedBy prop to identify who created the item, and you don't want that to change so this prop should not be in Modify
Pobiega
Pobiegaā€¢15mo ago
thats not a sideffect, its intentional Sure, if you have something like that then keep them separate
Angius
Angiusā€¢15mo ago
I'd rather just have DTOs self-contained for cases like that, by default But to each their own ofc
joren
jorenOPā€¢15mo ago
I never saw/used a record, I understand its a valuetype thats immutable. But isn't that somewhat what a struct entails, a readonly struct that is?
Angius
Angiusā€¢15mo ago
It's a reference type, actually It's a spicy class
joren
jorenOPā€¢15mo ago
but its members are value types?
Angius
Angiusā€¢15mo ago
Whether members are value or reference types depends on... their types A struct can have a reference-type member A class can have a value-type member
joren
jorenOPā€¢15mo ago
yes, on second thought that was a stupid question I wont lie this whole value type, reference type is new to me.
Angius
Angiusā€¢15mo ago
Records are classes that behave like value types (comparison by value) and have added immutability and some other useful stuff like a deconstructor Reference types are just pointers tbh Kinda-sorta
joren
jorenOPā€¢15mo ago
yes thats how I look at them but in C++ a reference != a ptr a reference is an alias, I suppose its the same for C# dtor's arent possible in a class/struct, normally?
Angius
Angiusā€¢15mo ago
They are But you need to write them yourself With records, you get it for free
joren
jorenOPā€¢15mo ago
there's no default dtor/ctor?
MODiX
MODiXā€¢15mo ago
Angius
REPL Result: Success
record Foo(int A, string B);
var f = new Foo(69, "nice");
var (a, b) = f;

new {a, b}
record Foo(int A, string B);
var f = new Foo(69, "nice");
var (a, b) = f;

new {a, b}
Result: <>f__AnonymousType0#1<int, string>
{
"a": 69,
"b": "nice"
}
{
"a": 69,
"b": "nice"
}
Compile: 594.961ms | Execution: 104.759ms | React with āŒ to remove this embed.
Angius
Angiusā€¢15mo ago
I'm not talking about a de-structor but about a de-con-structor
joren
jorenOPā€¢15mo ago
looks like brace initialization lol
Angius
Angiusā€¢15mo ago
(destructors in C# are called finalizers btw)
joren
jorenOPā€¢15mo ago
its basically construction it in place
Angius
Angiusā€¢15mo ago
var f = new Foo(69, "nice");
var (a, b) = f;
var f = new Foo(69, "nice");
var (a, b) = f;
this is the relevant bit tbh
joren
jorenOPā€¢15mo ago
struct A
{
int a;
};

A a = A{324};
struct A
{
int a;
};

A a = A{324};
var (a, b) = f; into a tuple just like that lol?
Angius
Angiusā€¢15mo ago
It's not a tuple, it's how you deconstruct... things that have deconstructors So tuples, records, anything else you write a deconstructor for
joren
jorenOPā€¢15mo ago
new term it seems deconstructors, basically help you grab the contents of a record into specific types
Angius
Angiusā€¢15mo ago
(swap in place with tuples is a cool trick btw, (a, b) = (b, a);)
joren
jorenOPā€¢15mo ago
yes bcs it doesnt care about the field names right? it just copies b into a, and a into b
Angius
Angiusā€¢15mo ago
Yep It's positional
joren
jorenOPā€¢15mo ago
ye, I would've uninstalled CLR if it wasnt šŸ˜„ Okay, so record's just have deconstructors that help you de-con-struct its properties into types much like going from a tuple into just, variables, conceptually that is?
MODiX
MODiXā€¢15mo ago
Angius
sharplab.io (click here)
record Foo(int A, string B);
record Foo(int A, string B);
Try the /sharplab command! | React with āŒ to remove this embed.
Angius
Angiusā€¢15mo ago
They have all of this, actually But yeah The deconstruction behaviour is like that of a tuple
joren
jorenOPā€¢15mo ago
(string, int) NameAndAge()
{
return ("dsfg", 21);
}
var (the_name, the_age) = NameAndAge();
(string, int) NameAndAge()
{
return ("dsfg", 21);
}
var (the_name, the_age) = NameAndAge();
much like this, conceptually
Angius
Angiusā€¢15mo ago
ye
joren
jorenOPā€¢15mo ago
though i prefer: (string name, int age) joren_data = NameAndAge(); we have auto in C++, which I use for long types but normally I'd say type clarity is important
Angius
Angiusā€¢15mo ago
You should be able to do that
joren
jorenOPā€¢15mo ago
in c sharp ppl seem to spam var, that scares me lol cus in C++ that usually result in ppl fucking up
MODiX
MODiXā€¢15mo ago
Angius
REPL Result: Success
(int a, string b) = (69, "nice");
(int a, string b) = (69, "nice");
Compile: 403.645ms | Execution: 61.001ms | React with āŒ to remove this embed.
Angius
Angiusā€¢15mo ago
ye
joren
jorenOPā€¢15mo ago
auto doesnt deduce reference for instance ye I do that, I think its better practice lol var should be limited to longer unbearable types to write
Angius
Angiusā€¢15mo ago
The recommendation in MS docs is to always use var
joren
jorenOPā€¢15mo ago
like this:
var t3 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
var t3 = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
Well, since C# is much more dummy proof than C++ I can see why they are okay with it but for new programmers spamming var is a good way to be unaware of what you're actually doing also becomes harder to read code sometimes lol not knowing the type instantly, especially if its not your own code
Angius
Angiusā€¢15mo ago
C# won't let you shoot your foot, thankfully. var isn't dynamic
Pobiega
Pobiegaā€¢15mo ago
yes and no. its hard if you read the code without an IDE, and people use bad names for variables/methods
Angius
Angiusā€¢15mo ago
And IDEs help
joren
jorenOPā€¢15mo ago
// from some utility file
var func()
{
return (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
}

var number_collection = func();
// from some utility file
var func()
{
return (1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26);
}

var number_collection = func();
Angius
Angiusā€¢15mo ago
Worst case scenario you're gonna get a red squiggle that you can't assign int to string ĀÆ\_(惄)_/ĀÆ Well, this func needs to have its return type
joren
jorenOPā€¢15mo ago
var wouldnt work?
Angius
Angiusā€¢15mo ago
No var works only for local variables Doesn't work for members
joren
jorenOPā€¢15mo ago
cryingcat actually really rough you'd end up with an using alias in this case then, still kind of sad it should be able to deduce the return type based on the return statement you'd think
Angius
Angiusā€¢15mo ago
That's why target-type new was introduced, so you don't have to do
private Foo _someField = new Foo();
private Foo _someField = new Foo();
and can just
private Foo _someField = new();
private Foo _someField = new();
instead of
var _someField = new Foo();
var _someField = new Foo();
joren
jorenOPā€¢15mo ago
this is inside a class?
Angius
Angiusā€¢15mo ago
Eh, I like it this way better, because the method signature is self-documenting Yeah, a field, a class member
joren
jorenOPā€¢15mo ago
i see, well its a nice addition will never use it
Angius
Angiusā€¢15mo ago
Although you can use target-type new everywhere
joren
jorenOPā€¢15mo ago
I believe types should be clarified in a class but maybe I need more of a dynamic way of thinking regarding C#
Angius
Angiusā€¢15mo ago
What do you mean by that?
joren
jorenOPā€¢15mo ago
Well when I look at a class, when I skim through one I havent written I'll look at the left part of it, the types and the names and thats how I get a quick grasp
Angius
Angiusā€¢15mo ago
Yeah
joren
jorenOPā€¢15mo ago
mind you that doing var _someField = new Foo(); is not normal in C++ and many languages noone does this, they use the ctor initializer list
Angius
Angiusā€¢15mo ago
Thankfully you cannot do that for class members
joren
jorenOPā€¢15mo ago
or just the ctor
Angius
Angiusā€¢15mo ago
Class members cannot use var
joren
jorenOPā€¢15mo ago
oh
Angius
Angiusā€¢15mo ago
That's why target-type new was made
joren
jorenOPā€¢15mo ago
whats with the field then can you show me a complete example
Pobiega
Pobiegaā€¢15mo ago
public Foo MyFoo { get; set; } = new();
Angius
Angiusā€¢15mo ago
Used to be:
private Foo _someField = new Foo();
private Foo _someField = new Foo();
Is now possible:
private Foo _someField = new();
private Foo _someField = new();
Was never possible, just a hypothetical:
var _someField = new Foo();
var _someField = new Foo();
Pobiega
Pobiegaā€¢15mo ago
thats a property with a default value
joren
jorenOPā€¢15mo ago
oh I see it now
Angius
Angiusā€¢15mo ago
Far as fields go... $structure
MODiX
MODiXā€¢15mo ago
namespace Namespace;

[Attribute]
public class Class
{
public string PublicField;
private bool _privateField;
protected double protectedField;

public int PublicProperty { get; set; }

public Class() {} // Constructor

public void Method(int parameter)
{
var localVariable = parameter;
}
}
namespace Namespace;

[Attribute]
public class Class
{
public string PublicField;
private bool _privateField;
protected double protectedField;

public int PublicProperty { get; set; }

public Class() {} // Constructor

public void Method(int parameter)
{
var localVariable = parameter;
}
}
For C# versions older than 10, see $StructureOld
joren
jorenOPā€¢15mo ago
public Class() {} // Constructor is not redundant lol whats w c sharp and having no default ctors unless explicitly defined?
Angius
Angiusā€¢15mo ago
It is redundant
joren
jorenOPā€¢15mo ago
oh I see, makes sense [Attribute], this flag, what does it mean?
Angius
Angiusā€¢15mo ago
It's metadata, really It can be... whatever
Pobiega
Pobiegaā€¢15mo ago
its an attribute, compile-time static metadata
Angius
Angiusā€¢15mo ago
And whatever you do with this metadata is up to you
Pobiega
Pobiegaā€¢15mo ago
mostly used for reflection
joren
jorenOPā€¢15mo ago
reflection is a thing in C#
Angius
Angiusā€¢15mo ago
You can use it with reflections, you can use it with sourcegen
joren
jorenOPā€¢15mo ago
now its getting scary
Angius
Angiusā€¢15mo ago
Half of C# frameworks, desktop and web, would not work without it lol
joren
jorenOPā€¢15mo ago
reflection is used often? or is it more a fancy feature ppl dont use
Angius
Angiusā€¢15mo ago
Now, thankfully, we have source gen nowadays
joren
jorenOPā€¢15mo ago
nvm sure is used often then lol
Angius
Angiusā€¢15mo ago
Yeah
joren
jorenOPā€¢15mo ago
Guess I'll be diving into it at some point
Angius
Angiusā€¢15mo ago
But Microsoft is pushing AOT now, and reflection is incompatible So support for source gen and other reflectionless ways increases
joren
jorenOPā€¢15mo ago
interesting, never used reflection if im being honest so suppose itll be my first time
Angius
Angiusā€¢15mo ago
You don't really use it that often manually I used it, like, 5 times Once because I was too lazy to register all my enums with the NpgSQL driver Twice because I wanted to add some automatic components for my project, that would generate fields for a form based on a class The first one is scheduled to be rewritten to a source generator
joren
jorenOPā€¢15mo ago
Mhm, def doesn't sound like a priority to learn for me then yet. Honestly wondering if my learning source is effective, im just reading the Tour of C# and spitting through the docs, works fine bcs of my programming experiences, but tons of details and not a lot of overview and best practices. Any recommendations for such?
Angius
Angiusā€¢15mo ago
I learn best by doing tbh ĀÆ\_(惄)_/ĀÆ So my recommendation would be... try to make something Google your way to completion Come here if you need ungoogleable help
Omnissiah
Omnissiahā€¢15mo ago
but with return types like that you can simplify your life by letting vs write it, anyway you write some crap and then use popup menu to correct it
joren
jorenOPā€¢15mo ago
Yeah or a type alias if its used in other places (the same exact type)
Angius
Angiusā€¢15mo ago
Yeah, you can type alias tuples nowadays Or... just make it a record lol
Omnissiah
Omnissiahā€¢15mo ago
or start writing it in a method assigning it to a var, and then select it and do extract method
Accord
Accordā€¢15mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server