Address D "technically" doesn't have 3.5 BTC in it, it rather has 1 BTC and 2 BTC and 0.5 BTC (which is a total of 3 inputs into address D).
I know this is a very interpreted way of explaining it but do I get that right?
Yes.
Am I the one that chooses which inputs from address D to send the BTC from or does the blockchain choose that?
You can choose any of your inputs. Some wallets can do that for you, but from the protocol perspective, any user can pick any coins, and also decide about their order.
If so, would the input that would cause the lowest fees be Address A since it would only be 1 input and 1 output?
It depends on the final transaction size in virtual bytes. You can prepare your transaction with zero fees, sign it, and then check, how many virtual bytes it takes, and then decrease output amounts however you want, to make transaction fee out of that.
Would it also make just 1 output as the entirety of the input of address A would be spent?
User can pick any inputs in any order, and user can also make any outputs, with any amounts, in any order, and with any address types. From the protocol level, user can make any transaction. As long as it is valid, it will be accepted. Which means, that you can send all coins to one address, as a single coin, but you can also split it between many smaller coins. Even if splitting is unnecessary, as long as transaction is valid, it will work.
If I were to send ~0.25 BTC (0.25 BTC - transaction fee) from address D to address E. Using address C as input, that would mean there would be 2 outputs. One output being address E and another output being address D as change (considering address D was set as the change address). Is that right?
+-------------------------------------------+
|+------------------+ +-----------------+|
|| Daniel 0.25 BTC | -> | Elaine 0.40 BTC ||
|| Charlie 0.50 BTC | | Dave 0.10 BTC ||
|+------------------+ +-----------------+|
+-------------------------------------------+
Is that what you wanted to achieve? All inputs of a given transaction are used to make all outputs of a given transaction. As long as things are signed with SIGHASH_ALL, everything is signed by each and every input. Which means, that you have just the sum of all inputs (0.25+0.50=0.75), and the sum of all outputs (0.40+0.10=0.50), and then the difference between them (0.75-0.50=0.25) is the transaction fee. Which means, that 0.40 BTC from Elaine does not come only from 0.25 BTC held by Daniel, or only from 0.50 BTC held by Charlie. It comes from both, unless you have signatures other than SIGHASH_ALL. And the same with Dave.
So, by default, you have all inputs, which are used to make all outputs, and nobody knows, which input was used to make which output. Of course, people can try to guess it, by looking at amounts, and judging, that if everyone transferred 0.01 BTC input to 0.009 BTC output, and one 1 BTC input was moved into one 0.999 BTC output, then they are likely to be connected, but from the protocol perspective, all inputs made all outputs, even if humans can guess it right, who sent what to whom.
In this situation, what would be the best way to calculate the fees for said transaction?
1. You prepare everything with zero fees.
2. You sign it with zero fees, and check, what is the size of your transaction.
3. You adjust output amounts, however you like.
4. Changing coin amounts does not affect transaction size, so it will have the same feerate.
5. You sign the final version, with non-zero fees, and broadcast it to the network.