❔ How to structure project and import items?
I'm just starting with C#, and I'm having trouble understanding how project structure and importing works.
I come from Rust where you can import a single item from a module with something like
You can also import multiple items using this notation:
I tried something similar in C#:
But that gives an error
Namespace name expected ...
. Is it not possible to only import some items from a namespace?
I'm also having trouble understanding the relationship between namespaces and files. It seems like you can add stuff to a namespace from anywhere inside your project. So it seems like namespaces are not really self-contained modules, but more like heaps where you can throw in anything semi-related to some concept.
Finally it seems to me like you can't have items private to a module?
This all seems rather unstructured to me, so surely I must be missing something. I can summarize my questions as such:
1. Can you import only specific items?
2. What's the idiomatic way to structure complex libraries? namespace per file?
3. What are the tools to restrict visibility such that I can have parts of my library that are only available internally?
Please bear in mind I'm not familiar with "old-school" OOP stuff so I might be oblivious to the obvious 🙂 I would highly appreciate it if you could explain how it works.41 Replies
"import" isnt really a concept in C# as such.
using namespace.namespace.namespace;
just allows you to "use" the things from the target namespace without fully qualifying it
even without the using statement, you can still use them with their full name
like Namespace.Type varName = new Namespace.Type();
the closest thing we have to rusts modules are the C# assemblies
each project compiles to its own assembly, and its fairly common to split a larger codebase into multiple projects
namespaces traditionally reflect your file structure, but there are exceptions
Finally it seems to me like you can't have items private to a module?Sure you can, thats
internal
(as opposed to public
or private
)
it means "only this assembly can use this type"for 2, namespaces normally reflect the folder structure of your project (so one namespace per folder, not file)
technically you could "import" individual types using type aliases but i don't think i've ever seen that used except in the case where you have ambiguous types
^
and yeah, if you're developing a library then simply marking types as
internal
instead of public
will hide them from consumersUsually in my experience this is a tooling gaffe; not intentional.
yeah, like i said i've never seen someone actually do that
Thank you both @Pobiega @Jimmacle, that does clear things up a bit.
So, for "importing specific items" (I now understand this is not quite the correct terminology), my options are either 1) qualify the items everywhere, or 2) deal with the fact that I'm importing everything in the module?
As for assemblies and
internal
, this is good to know, but still a bit unfortunate because in Rust it's very common to have many very contained modules that only export their public API and keep all the internals private. The goal is not only to keep things away from consumers, but also to restrict which parts of a codebase can interact with each other's internals.
I imagine it's not super good practice to have a ton of assemblies, and AFAIK you can't nest assemblies.
But anyways, since this is the way things are done in C# I'll learn and adapt 🙂You can make nested classes, and have a nested class be
private
that would make it unusable from outside that classI have some data scenarios where I use private nested classes for that reason.
same
Is it a common pattern, to use a whole class as a module? That is very foreign to me.
"module" is foreign to us btw 😛
yeah i'm not super clear on what expectations there are for a "module" as a filthy non-rust-user
but based on my very limited understanding of rust, I'd say... kinda? 😄
well, I'm the beginner here, so you'll have to excuse me 🙂 I don't have the right terminology.
An "assembly" could be a single class, but frequently that's not the case.
I'm learning though!
as for 2, there isn't really a penalty for this besides maybe making it harder to find what you're looking for in autocomplete
to me, a module is a list of items (types, functions, etc), which can declare the items as private or public. Modules can be nested, and nested modules have access to their parents internals. Parent modules don't have access to their childen's internals.
This is not super important, but I thought I'd clarify to make my understanding clearer.
Referring to namespace-per-file? I don't mind namespace-per-folder if that's what's usually done.
I'm just trying to understand best practices and such. Visibility is a huge deal in Rust so that's why I might seem over-focused on that. But ultimately I just want to do C# the right way.
i just mean there's no impact on compile times etc. (at least not that i've run into)
namespace-per-folder is the default, and most if not all editors do it by default
yeah, afaik rider's defaults will nag you to make your namespaces match your folder structure
there is no system for fine-grained imports in C#. it does not really make sense for .NET, where the smallest unit of organization is the assembly. as soon as you reference a DLL, all of the types (which are visible to you) are already "imported"
VS too
we don't have top level functions as such. all methods, including static methods, must belong to a class (or other type), so the only group of things we have in a namespace are types
oh! No free functions? That is strange 🤔 But good to know!
in other languages, fine-grained imports may make more sense, because the types really cannot be used without the import. in JavaScript, importing something runs some code, and if you don't run the code, the things you're trying to import just aren't there. but in .NET, once you have referenced the standard library,
System.Collections.Generic.List
is always there. in C#, you can always reference it by its fully-qualified name, even without a using
statement. so having fine-grained imports does not bring much to the tablemight feel strange at first yep.. but if you make a static class with only static methods in it... thats essentially a module from rust
a budget module, or a "we have modules at home" module perhaps
I see, this is all good to know, thanks again
the closest thing to a module, i guess, is a static class
IMO the benefit is more about dev experience and not so much about how the compiler handles linking and whatnot.
using
a namespace in C# seems to be akin to doing use my_module::*;
in Rust which is considered bad practice since it pulls at ton of potentially useless crap into scope and increases the chance of name collisions.in C#, you can use
using static X;
to directly import all static members of a class. so, for example, you might find it usefulvery rarely used in C# thou and is a bit controversial
when it makes sense, its great thou
Yeah Rider suggested I do that but it felt a bit strange lol But I'll keep it in mind when it makes sense
this isn't really something people care about in .NET
I think a reason for that is we've had really good editor support since day 1. I really struggled trying to use non C# languages because lack of autocomplete and intellisense just crippled me
and i personally find it weird that languages would choose to optimize their import system for preventing naming collisions, which are pretty rare (especially since, in .NET, people do not usually give things generic names)
and since we don't have free functions,
Add
isnt a thing - its something.Add
or Something.Add
It's not just about name collision, it also helps you as a dev to understand precisely where items come from and be parsimonious about what's in scope.
If we're talking about auto-complete, then having the smallest list of stuff in scope is also beneficial to skim through what's available.
less stuff == easier to wrap your head aroundm
anyways, I'm not really here to debate things, I'm committed to doing things the C# way 😄
I already learned a ton from you guys, I'm very grateful for you all taking the time to explain things 🙂
"namespace pollution" is not really a concern in the .NET world. as an extreme example of this, you can add 'global' imports which automatically apply to every file https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive#global-modifier
and, when you create a new project, a number of global imports are added automatically
and libraries are designed around these coarse imports. in .NET it's very rare to just use one or two classes from a namespace. writing a class is easy, so people create lots of them! classes and extension methods which are frequently used together are generally put in the same namespace so that access to them is easy
I see
This gives some context to the "why", thanks for explaining 🙂
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.